A containerized OpenStreetMap tile server environment built on Ubuntu 24.04 and PostgreSQL 16. This project modernizes the original OSM tile server implementation with updated components and optimized configurations.
Terradock provides a complete solution for running your own OpenStreetMap tile server with the latest stack:
- Base OS: Ubuntu 24.04 LTS
- Database: PostgreSQL 16 with PostGIS 3
- Rendering: Mapnik, mod_tile, and renderd
- Import Tools: osm2pgsql and osmosis
- Web Interface: Leaflet-based viewer
- Modern Stack: Uses the latest Ubuntu 24.04 LTS and PostgreSQL 16
- Multi-stage Docker Build: Optimized container size and build process
- Configurable Rendering: Customize threads, styles, and database settings
- Regional Data Support: Import specific regions and filter updates
- Automatic Updates: Optional hourly updates from OpenStreetMap
- Performance Optimized: Database and rendering configurations tuned for modern hardware
- Flexible Deployment: Suitable for personal use through production environments
- Docker and Docker Compose
- At least 4GB RAM (8GB+ recommended)
- Sufficient disk space for OSM data:
- Small region: 20GB+
- Country: 100GB+
- Continent/Global: 500GB+
-
Pull the image from GitHub Container Registry:
docker pull ghcr.io/sriram-pr/terradock:latest
-
Create Docker volumes for persistent data:
docker volume create osm-data docker volume create osm-tiles
-
Import OpenStreetMap data (Luxembourg example):
docker run \ -e DOWNLOAD_PBF="https://download.geofabrik.de/europe/luxembourg-latest.osm.pbf" \ -e DOWNLOAD_POLY="https://download.geofabrik.de/europe/luxembourg.poly" \ -e UPDATES=enabled \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ ghcr.io/sriram-pr/terradock:latest \ import -
Run the tile server:
docker run \ -p 8080:80 \ -e UPDATES=enabled \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ -d ghcr.io/sriram-pr/terradock:latest \ run -
Access the web interface at
http://localhost:8080
-
Clone this repository:
git clone https://github.com/Sriram-PR/terradock-osm-tile-server.git cd terradock-osm-tile-server -
Build the Docker image:
docker build -t terradock . -
Create Docker volumes for persistent data:
docker volume create osm-data docker volume create osm-tiles
-
Import OpenStreetMap data (Luxembourg example):
docker run \ -e DOWNLOAD_PBF="https://download.geofabrik.de/europe/luxembourg-latest.osm.pbf" \ -e DOWNLOAD_POLY="https://download.geofabrik.de/europe/luxembourg.poly" \ -e UPDATES=enabled \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ terradock \ import -
Run the tile server:
docker run \ -p 8080:80 \ -e UPDATES=enabled \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ -d terradock \ run -
Access the web interface at
http://localhost:8080
Instead of downloading data, you can use your own OSM PBF file:
docker run \
-v /absolute/path/to/your-region.osm.pbf:/data/region.osm.pbf \
-v osm-data:/data/database/ \
-v osm-tiles:/data/tiles/ \
terradock \
importFor regional updates, include the corresponding polygon file (highly recommended):
docker run \
-e UPDATES=enabled \
-v /absolute/path/to/your-region.osm.pbf:/data/region.osm.pbf \
-v /absolute/path/to/your-region.poly:/data/region.poly \
-v osm-data:/data/database/ \
-v osm-tiles:/data/tiles/ \
terradock \
importImportant: The polygon file is crucial for efficient updates, as it filters OSM changes to just your region of interest. Without it, your server will process all global changes, which can be extremely resource-intensive.
| Variable | Phase | Description | Default |
|---|---|---|---|
THREADS |
Both | Number of threads for importing/rendering | 4 |
UPDATES |
Both | Enable automatic updates (must be set in both import and run) | disabled |
FLAT_NODES |
Both | Use flat nodes file for large imports | disabled |
PGPASSWORD |
Both | PostgreSQL password for renderd user | renderd |
ALLOW_CORS |
Run | Enable CORS headers | disabled |
NAME_LUA |
Import | Custom Lua script name | openstreetmap-carto.lua |
NAME_STYLE |
Import | Custom style file name | openstreetmap-carto.style |
NAME_MML |
Import | Custom CartoCSS project file | project.mml |
NAME_SQL |
Import | Custom SQL indexes file | indexes.sql |
DOWNLOAD_PBF |
Import | URL to download OSM PBF data | NULL |
DOWNLOAD_POLY |
Import | URL to download region polygon | NULL |
WGET_ARGS |
Import | Additional arguments for wget | NULL |
PG_VERSION |
Both | PostgreSQL version | 16 |
AUTOVACUUM |
Both | Control PostgreSQL autovacuum | on |
OSM2PGSQL_EXTRA_ARGS |
Import | Additional args for osm2pgsql | "" |
REPLICATION_URL |
Both | URL for updates | https://planet.openstreetmap.org/replication/hour/ |
MAX_INTERVAL_SECONDS |
Both | Max time between updates | 3600 |
EXPIRY_MINZOOM |
Run | Minimum zoom for tile expiry | 13 |
EXPIRY_TOUCHFROM |
Run | Minimum zoom to mark tiles dirty | 13 |
EXPIRY_DELETEFROM |
Run | Minimum zoom to delete tiles | 19 |
EXPIRY_MAXZOOM |
Run | Maximum zoom for tile expiry | 20 |
Note: Variables marked as "Both" should be set identically during both import and run phases for proper operation.
Resources needed vary based on your deployment size:
| Deployment | RAM | CPU | Storage | Example Use Case |
|---|---|---|---|---|
| Minimal | 4GB | 2 | 20GB | Small region/city |
| Recommended | 8GB | 4 | 100GB | Country-level |
| Production | 16GB+ | 8+ | 500GB+ | Continental/Global |
The system architecture consists of several integrated components:
- PostgreSQL + PostGIS: Spatial database storing OSM data
- osm2pgsql: Imports OpenStreetMap data into PostgreSQL
- Osmosis: Handles incremental updates from OpenStreetMap
- Mapnik: Rendering library that converts data to tiles
- mod_tile/renderd: Manages tile rendering and caching
- Apache: Web server that delivers tiles and the viewer interface
The container uses volumes mounted at specific paths:
/data/
├── database/ # PostgreSQL database files and flat nodes
│ ├── postgres/ # PostgreSQL data directory
│ ├── flat_nodes.bin # Optional flat nodes file for large imports
│ └── region.poly # Optional polygon file for regional updates
├── style/ # Stylesheet files
│ ├── mapnik.xml # Compiled Mapnik XML style
│ ├── project.mml # CartoCSS project file
│ └── *.style/*.lua # osm2pgsql style files
├── tiles/ # Rendered tile cache
└── region.osm.pbf # OSM data file to import
Understanding when and how to use key parameters is crucial for optimal operation:
- Import Phase: Controls number of parallel processes for osm2pgsql database import
-e THREADS=8 terradock import
- Run Phase: Controls number of rendering threads for renderd tile generation
-e THREADS=8 terradock run
- Best Practice: Set this in BOTH phases, but can use different values based on workload (higher for import, lower for rendering if needed)
- Import Phase: Enables the flat nodes file for efficient memory usage during large imports
-e FLAT_NODES=enabled terradock import
- Run Phase: Ensures the system knows where to find the flat nodes file
-e FLAT_NODES=enabled terradock run
- Best Practice: Set this in BOTH phases if you use it at all, and keep it consistent
- Import Phase ONLY: Provides additional arguments to osm2pgsql during import
-e OSM2PGSQL_EXTRA_ARGS="-C 4096 --drop" terradock import - Common Uses:
-C <cache size>: Memory cache for import (larger = faster)--drop: Drop tables before import (for reimporting)--slim: Keep temporary tables (default in script)--hstore-all: Store all tags in the hstore column
- Best Practice: Only needed during import, not used during run phase
| Parameter | Import | Run | Notes |
|---|---|---|---|
| THREADS | ✅ | ✅ | Set in both, but values can differ |
| FLAT_NODES | ✅ | ✅ | Must be consistent if used |
| OSM2PGSQL_EXTRA_ARGS | ✅ | ❌ | Import phase only |
For large imports (country or larger), a recommended configuration would be:
# Import phase
docker run -e THREADS=12 -e FLAT_NODES=enabled -e OSM2PGSQL_EXTRA_ARGS="-C 4096" terradock import
# Run phase
docker run -e THREADS=8 -e FLAT_NODES=enabled terradock runPostgreSQL 16 configuration uses hardcoded settings in the template file. Consider modifying these values based on your hardware:
shared_buffers = 2GB
work_mem = 256MB
maintenance_work_mem = 512MB
effective_cache_size = 4GB
max_parallel_workers = 8
shared_buffers = 4GB
work_mem = 512MB
maintenance_work_mem = 1GB
effective_cache_size = 8GB
max_parallel_workers = 16
Edit the postgresql.custom.conf.tmpl file and rebuild the image for your specific hardware.
Rendering performance can be tuned via:
THREADSenvironment variable to control rendering threadsOSM2PGSQL_EXTRA_ARGS="-C <cache size in MB>"for import cache (default: 800MB)- Tile expiry settings in the update script
To use a custom style, mount it into the /data/style/ directory:
docker run -p 80:80 \
-v /path/to/data:/data \
-v /path/to/custom-style:/data/style \
terradock runYour style should include:
- CartoCSS project file (.mml)
- osm2pgsql style files (.style and .lua)
- Any additional resources like shapefiles or SQL scripts
When automatic updates are enabled (UPDATES=enabled):
- The system downloads changeset files from OpenStreetMap's replication server
- Changes are filtered to your region of interest (if a polygon is defined)
- Updates are applied to the PostgreSQL database
- Affected tiles are marked for re-rendering based on zoom level settings:
- Zoom 13-18: Marked for re-rendering
- Zoom 19-20: Deleted (to save space)
The update process runs automatically via cron. Important: The UPDATES flag must be set during both import and run phases:
# During import - sets up the initial osmosis workspace
docker run \
-e UPDATES=enabled \
-v osm-data:/data/database/ \
-v osm-tiles:/data/tiles/ \
terradock \
import
# During run - activates the cron job
docker run \
-p 8080:80 \
-e UPDATES=enabled \
-v osm-data:/data/database/ \
-v osm-tiles:/data/tiles/ \
-d terradock \
runTo use a different update frequency:
docker run \
-p 8080:80 \
-e UPDATES=enabled \
-e REPLICATION_URL=https://planet.openstreetmap.org/replication/minute/ \
-e MAX_INTERVAL_SECONDS=60 \
-v osm-data:/data/database/ \
-v osm-tiles:/data/tiles/ \
-d terradock \
runTo connect to the PostgreSQL database:
docker run \
-p 8080:80 \
-p 5432:5432 \
-v osm-data:/data/database/ \
-d terradock \
runConnect using:
psql -h localhost -U renderd gisThe default password is renderd but can be changed using the PGPASSWORD environment variable.
For production environments:
-
Scaling:
- Use higher
THREADSvalues on multi-core systems - Consider separate database and rendering servers for large deployments
- Use higher
-
Security:
- Set a strong
PGPASSWORD - Use a reverse proxy with SSL termination
- Restrict access to the PostgreSQL port
- Set a strong
-
High Availability:
- Implement a CDN for tile caching
- Consider load balancing across multiple rendering servers
- Set up regular database backups
-
Monitoring:
- Monitor disk space for the database and tile cache
- Track rendering queue length and performance
- Set up alerts for service disruptions
Create a docker-compose.yml file:
version: '3'
services:
terradock:
build: .
ports:
- "8080:80"
volumes:
- osm-data:/data/database
- osm-tiles:/data/tiles
environment:
- THREADS=4
- UPDATES=enabled
command: run
volumes:
osm-data:
osm-tiles:Run with:
docker-compose up -d| Issue | Possible Causes | Solution |
|---|---|---|
| Slow initial rendering | Normal for first-time tile generation | Tiles are cached after first rendering |
| Database connection errors | PostgreSQL not started or misconfigured | Check PostgreSQL logs and configuration |
| Missing fonts | Font packages not installed | Ensure all required font packages are installed |
| Out of memory | Insufficient RAM for dataset size | Increase available memory or reduce shared_buffers |
| Slow updates | Large update files or regional filtering | Check update logs for bottlenecks |
| "No space left on device" | Shared memory limit too low | Add --shm-size="192m" to docker run command |
| Import process crashes | Memory limitations | Try enabling FLAT_NODES=enabled or reduce cache size |
| Update script fails | Path mismatch in update script | Verify that the script uses the correct path to trim_osc.py |
For importing very large regions or the entire planet, use the FLAT_NODES option to improve performance and reduce memory requirements:
docker run \
-v /path/to/planet.osm.pbf:/data/region.osm.pbf \
-v osm-data:/data/database/ \
-e "FLAT_NODES=enabled" \
-e "OSM2PGSQL_EXTRA_ARGS=-C 4096" \
terradock \
importWhen using FLAT_NODES=enabled, ensure you set it for both import and run phases.
If you encounter errors like "could not resize shared memory segment" or "No space left on device", increase the shared memory limit:
docker run \
-p 8080:80 \
-v osm-data:/data/database/ \
--shm-size="192m" \
-d terradock \
runImportant logs are available in the container at:
/var/log/tiles/run.log: Main operation log/var/log/tiles/osmosis.log: Update download log/var/log/tiles/osm2pgsql.log: Database import log/var/log/tiles/expiry.log: Tile expiry log/var/log/apache2/error.log: Web server errors
The tile cache grows continuously as users request new areas and zoom levels. Without proper management, it can consume hundreds of gigabytes or even terabytes of disk space:
# Check current tile cache size
docker exec -it [container_name] du -sh /data/tiles
# Clear the entire tile cache (use with caution)
docker exec -it [container_name] rm -rf /data/tiles/*For selective cache management, consider these strategies:
-
Age-Based Pruning: Remove tiles older than a certain date:
docker exec -it [container_name] find /data/tiles -type f -mtime +90 -delete -
Zoom-Level Pruning: Remove only high-zoom tiles that are less frequently accessed:
docker exec -it [container_name] find /data/tiles -path "*/[17-20]/*" -type f -delete
-
Scheduled Maintenance: Add a cron job to periodically manage the cache:
0 3 * * 0 docker exec [container_name] find /data/tiles -type f -mtime +60 -delete
While PostgreSQL's autovacuum handles basic maintenance, long-running OSM databases benefit from additional care:
-
Manual VACUUM: Run a full vacuum analyze periodically:
docker exec -it [container_name] sudo -u postgres psql -d gis -c "VACUUM ANALYZE;"
-
Reindex: Rebuild indexes for better performance:
docker exec -it [container_name] sudo -u postgres psql -d gis -c "REINDEX DATABASE gis;"
-
Scheduled Maintenance Script:
#!/bin/bash # Run weekly database maintenance docker exec [container_name] sudo -u postgres psql -d gis -c "VACUUM ANALYZE;" # Monthly reindexing if [ $(date +%d) -eq "01" ]; then docker exec [container_name] sudo -u postgres psql -d gis -c "REINDEX DATABASE gis;" fi
-
PostgreSQL Tuning: For long-term operation, consider adjusting these settings in
postgresql.custom.conf.tmpl:# Increase for large databases maintenance_work_mem = 1GB # More aggressive autovacuum autovacuum_vacuum_threshold = 1000 autovacuum_analyze_threshold = 500
Set up basic monitoring to ensure smooth operation:
-
Disk Usage:
# Monitor database size docker exec [container_name] sudo -u postgres psql -d gis -c "SELECT pg_size_pretty(pg_database_size('gis'));" # Monitor tile cache growth docker exec [container_name] du -sh /data/tiles
-
Rendering Queue:
# Check current rendering queue docker exec [container_name] sudo -u renderd renderd -t
-
Update Lag:
# Check update lag (how far behind is your database) docker exec [container_name] /usr/bin/osmosis-db_replag
-
Service Status:
# Check if services are running docker exec [container_name] service postgresql status docker exec [container_name] service apache2 status docker exec [container_name] ps aux | grep renderd
Proper restart procedures prevent data corruption:
-
Graceful Shutdown:
# Properly stop the container docker stop -t 120 [container_name]The extended timeout (120 seconds) allows PostgreSQL to shut down properly.
-
After System Reboot:
# Start the container docker start [container_name] # Verify services docker exec [container_name] service postgresql status docker exec [container_name] service apache2 status docker exec [container_name] ps aux | grep renderd
-
Recovery from Improper Shutdown: If PostgreSQL won't start after an improper shutdown:
# Run database recovery docker exec -it [container_name] sudo -u postgres pg_ctl -D /data/database/postgres/ recover docker exec -it [container_name] service postgresql start
When upgrading to a new container version:
-
Data Backup:
# Create a backup of your volumes docker run --rm -v osm-data:/data -v $(pwd):/backup alpine tar -czf /backup/osm-data-backup.tar.gz /data docker run --rm -v osm-tiles:/data -v $(pwd):/backup alpine tar -czf /backup/osm-tiles-backup.tar.gz /data
-
Simple Upgrade (for minor version changes):
# Pull the new image docker pull ghcr.io/sriram-pr/terradock:latest # Stop and remove the old container docker stop [container_name] docker rm [container_name] # Start a new container with the same volumes docker run -p 8080:80 -v osm-data:/data/database -v osm-tiles:/data/tiles -d --name [container_name] ghcr.io/sriram-pr/terradock:latest run
-
Database Migration (for major PostgreSQL version changes): For significant version changes that require a database upgrade:
# Export the database docker exec -it [old_container] sudo -u postgres pg_dump -Fc gis > gis_backup.dump # Start new container in import mode docker run -v $(pwd):/backup -v new-osm-data:/data/database -v new-osm-tiles:/data/tiles --name new-container ghcr.io/sriram-pr/terradock:latest import # Restore the database docker exec -it new-container sudo -u postgres pg_restore -d gis /backup/gis_backup.dump # Start in run mode docker stop new-container docker run -p 8080:80 -v new-osm-data:/data/database -v new-osm-tiles:/data/tiles -d --name new-container ghcr.io/sriram-pr/terradock:latest run
The default cron configuration runs the update script every minute (* * * * *), which is excessive for most deployments. To customize:
-
Access the container:
docker exec -it [container_name] bash -
Edit the crontab:
nano /etc/crontab
-
Modify the update frequency:
# Default (every minute) * * * * * renderd openstreetmap-tiles-update-expire.sh # Every 15 minutes */15 * * * * renderd openstreetmap-tiles-update-expire.sh # Hourly 0 * * * * renderd openstreetmap-tiles-update-expire.sh # Every 6 hours 0 */6 * * * renderd openstreetmap-tiles-update-expire.sh # Daily at 2 AM 0 2 * * * renderd openstreetmap-tiles-update-expire.sh -
Restart cron:
service cron restart
-
For persistence, create a custom Dockerfile:
FROM ghcr.io/sriram-pr/terradock:latest COPY custom_crontab /etc/crontab
Choose the right update frequency based on your needs:
| Frequency | Configuration | CPU/IO Impact | Update Lag | Best For |
|---|---|---|---|---|
| Minute | REPLICATION_URL=.../minute/ & cron: * * * * * |
Very High | ~1 minute | Real-time apps, emergency services |
| 15 Minutes | REPLICATION_URL=.../minute/ & cron: */15 * * * * |
High | ~15 minutes | Urban navigation, delivery services |
| Hourly | REPLICATION_URL=.../hour/ & cron: 0 * * * * |
Medium | ~1 hour | General use, balanced approach |
| 6 Hours | REPLICATION_URL=.../hour/ & cron: 0 */6 * * * |
Low | ~6 hours | Standard mapping applications |
| Daily | REPLICATION_URL=.../day/ & cron: 0 2 * * * |
Very Low | ~1 day | Static maps, rural areas |
Example for hourly updates:
docker run -p 8080:80 \
-e UPDATES=enabled \
-e REPLICATION_URL=https://planet.openstreetmap.org/replication/hour/ \
-e MAX_INTERVAL_SECONDS=3600 \
-v osm-data:/data/database/ \
-v osm-tiles:/data/tiles/ \
-d terradock \
runTile expiry settings control which zoom levels get re-rendered or deleted after updates:
| Setting | Description | Default |
|---|---|---|
EXPIRY_MINZOOM |
Minimum zoom to consider for expiry | 13 |
EXPIRY_TOUCHFROM |
Minimum zoom to mark as dirty | 13 |
EXPIRY_DELETEFROM |
Minimum zoom to delete instead of re-rendering | 19 |
EXPIRY_MAXZOOM |
Maximum zoom to consider for expiry | 20 |
Example configurations:
-
Low-resource server (minimize rendering load):
-e EXPIRY_MINZOOM=13 \ -e EXPIRY_TOUCHFROM=13 \ -e EXPIRY_DELETEFROM=16 \ -e EXPIRY_MAXZOOM=18
This deletes higher zoom tiles (16+) instead of re-rendering them, reducing CPU load.
-
High-performance server (maximize quality):
-e EXPIRY_MINZOOM=10 \ -e EXPIRY_TOUCHFROM=10 \ -e EXPIRY_DELETEFROM=20 \ -e EXPIRY_MAXZOOM=20
This re-renders all affected tiles from zoom 10 to 19, ensuring maximum freshness.
-
Balanced approach:
-e EXPIRY_MINZOOM=12 \ -e EXPIRY_TOUCHFROM=12 \ -e EXPIRY_DELETEFROM=18 \ -e EXPIRY_MAXZOOM=20
To implement these settings:
docker run -p 8080:80 \
-e UPDATES=enabled \
-e EXPIRY_MINZOOM=13 \
-e EXPIRY_TOUCHFROM=13 \
-e EXPIRY_DELETEFROM=18 \
-e EXPIRY_MAXZOOM=20 \
-v osm-data:/data/database/ \
-v osm-tiles:/data/tiles/ \
-d terradock \
runHere are recommended configurations for common use cases:
-
Small City/Local Deployment (minimal hardware, <4GB RAM):
# Import docker run \ -e THREADS=2 \ -e DOWNLOAD_PBF="https://download.geofabrik.de/europe/luxembourg-latest.osm.pbf" \ -e DOWNLOAD_POLY="https://download.geofabrik.de/europe/luxembourg.poly" \ -e UPDATES=enabled \ -e OSM2PGSQL_EXTRA_ARGS="-C 500" \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ terradock import # Run docker run -p 8080:80 \ -e THREADS=2 \ -e UPDATES=enabled \ -e EXPIRY_DELETEFROM=16 \ -e EXPIRY_MAXZOOM=18 \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ -d terradock run
Recommended cron: Daily updates (
0 2 * * *) -
Country-Level Deployment (8GB RAM):
# Import docker run \ -e THREADS=4 \ -e FLAT_NODES=enabled \ -e DOWNLOAD_PBF="https://download.geofabrik.de/europe/germany-latest.osm.pbf" \ -e DOWNLOAD_POLY="https://download.geofabrik.de/europe/germany.poly" \ -e UPDATES=enabled \ -e OSM2PGSQL_EXTRA_ARGS="-C 2048" \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ terradock import # Run docker run -p 8080:80 \ -e THREADS=4 \ -e FLAT_NODES=enabled \ -e UPDATES=enabled \ -e EXPIRY_MINZOOM=12 \ -e EXPIRY_TOUCHFROM=12 \ -e EXPIRY_DELETEFROM=18 \ -e EXPIRY_MAXZOOM=19 \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ -d terradock run
Recommended cron: Hourly updates (
0 * * * *) -
Continental/Production Deployment (16GB+ RAM):
# Import docker run \ -e THREADS=8 \ -e FLAT_NODES=enabled \ -e DOWNLOAD_PBF="https://download.geofabrik.de/europe-latest.osm.pbf" \ -e DOWNLOAD_POLY="https://download.geofabrik.de/europe.poly" \ -e UPDATES=enabled \ -e OSM2PGSQL_EXTRA_ARGS="-C 8192 --number-processes 8" \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ terradock import # Run docker run -p 8080:80 \ -e THREADS=6 \ -e FLAT_NODES=enabled \ -e UPDATES=enabled \ -e EXPIRY_MINZOOM=10 \ -e EXPIRY_TOUCHFROM=10 \ -e EXPIRY_DELETEFROM=19 \ -e EXPIRY_MAXZOOM=20 \ -v osm-data:/data/database/ \ -v osm-tiles:/data/tiles/ \ --shm-size=512m \ -d terradock run
Recommended cron: 15-minute updates (
*/15 * * * *)
For importing the entire planet or very large regions:
docker run \
-v /path/to/planet.osm.pbf:/data/region.osm.pbf \
-v osm-data:/data/database/ \
-e "FLAT_NODES=enabled" \
-e "OSM2PGSQL_EXTRA_ARGS=-C 4096" \
terradock \
importFor systems without internet access:
- Import data on an internet-connected system
- Export the volumes:
docker run --rm -v osm-data:/data -v $(pwd):/backup alpine tar -czf /backup/osm-data.tar.gz /data docker run --rm -v osm-tiles:/data -v $(pwd):/backup alpine tar -czf /backup/osm-tiles.tar.gz /data
- Transfer the tar files to the target system
- Restore volumes:
docker volume create osm-data docker volume create osm-tiles docker run --rm -v osm-data:/data -v $(pwd):/backup alpine tar -xzf /backup/osm-data.tar.gz -C / docker run --rm -v osm-tiles:/data -v $(pwd):/backup alpine tar -xzf /backup/osm-tiles.tar.gz -C /
The tile server uses Apache with mod_tile to serve map tiles. The configuration file (apache.conf) contains:
<VirtualHost *:80>
ServerAdmin webmaster@localhost
AddTileConfig /tile/ default
LoadTileConfigFile /etc/renderd.conf
ModTileRenderdSocketName /run/renderd/renderd.sock
ModTileRequestTimeout 0
ModTileMissingRequestTimeout 30
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<IfDefine ALLOW_CORS>
Header set Access-Control-Allow-Origin "*"
</IfDefine>
</VirtualHost>Key settings:
- AddTileConfig: Maps the
/tile/URL path to the "default" renderd configuration - ModTileRenderdSocketName: Specifies the socket used to communicate with the renderd daemon
- ModTileRequestTimeout: Set to 0 (no timeout) for rendering requests
- ModTileMissingRequestTimeout: 30-second timeout for missing tiles
To enable CORS for serving tiles to other domains:
docker run \
-p 8080:80 \
-v osm-data:/data/database/ \
-e ALLOW_CORS=enabled \
-d terradock \
runThis activates the <IfDefine ALLOW_CORS> section in the Apache configuration, adding the necessary CORS headers.
This repository improves upon the original OSM tile server in several ways:
- Modernized Stack: Updated to Ubuntu 24.04 and PostgreSQL 16
- Optimized Configurations: Better tuned for modern hardware
- Enhanced Containerization: Multi-stage builds and proper volume management
- Improved Documentation: Comprehensive setup and tuning guides
- Simplified Deployment: Streamlined import and run processes
- Consolidated Flat Nodes Handling: Simplified approach to handling flat nodes
- PostgreSQL Configuration: The PostgreSQL configuration has hardcoded memory settings that might not be optimal for all deployment environments. Consider modifying the
postgresql.custom.conf.tmplfile for your specific hardware.
The project comprises several key files:
- Dockerfile: Multi-stage build for the container
- postgresql.custom.conf.tmpl: Database configuration template
- openstreetmap-tiles-update-expire.sh: Update and tile expiry script
- run.sh: Main entry script controlling import and run modes
- apache.conf: Apache web server configuration for tile serving
- leaflet-demo.html: Web interface for viewing tiles
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the Apache-2.0 License.
- OpenStreetMap project for the original software
- All contributors to the OpenStreetMap ecosystem
- The PostgreSQL and PostGIS communities