Map Server Flow
[!NOTE] For installation and local maps, see Map services.
On this page
Design Principles
- Hosted by default - normal app and demo deployments use the hosted PMTiles service through the Java backend proxy.
- Local sidecar is optional - Docker Compose keeps
map-servervisible under thelocal-mapsprofile. It is not started by default. - No baked map archives - the
mytraillog-mapsimage contains nginx, the PMTiles CLI, and the orchestrator only. Map data lives on the/datavolume or is prepared at runtime. - Versioned tile files - every tile file is named by its Protomaps build
date:
YYYYMMDD.pmtiles. - Stable symlinks -
planet.pmtilesandworld-lowzoom.pmtilesare symbolic links pointing to the latest complete versioned files. - Sentinel files - a
.donefile next to a versioned tile means it is complete. Symlinks are created only after the sentinel exists. - Resume safety - on restart, the orchestrator resumes an incomplete
YYYYMMDD.pmtilesversion instead of switching to a newer build. - Java proxy owns upstream choice - the browser calls
/mtl/api/map-proxy/prod/...for both hosted and local PMTiles. The backend chooses local sidecar when healthy, otherwise hosted.
Build Time
docker-build-deploy.py builds and pushes the map image like any other image.
It no longer probes Protomaps builds and no longer passes map build arguments.
The Dockerfile builds one lightweight runtime image:
- Alpine
- nginx
- python3
pmtilesCLI- orchestrator scripts
- entrypoint
No /data-prebuilt directory is copied into the image.
Container Startup
entrypoint.sh
The entrypoint directly execs:
python3 /app/scripts/orchestrator.py
orchestrator.py
- Sets
os.umask(0)so created files are manageable from mounted volumes. - Installs signal handlers for
SIGTERMandSIGINT. - Starts nginx on port 8081. Docker health checks can pass immediately.
- Starts an internal kickoff HTTP server on
127.0.0.1:8082. - Writes
status=waiting-kickoff. - Starts a background worker thread that waits for kickoff requests.
- Keeps nginx in the foreground process group.
Backend Startup
MapServerKickoffService sends a kickoff only when:
mtl.map-server.tile-modeislocal- a tile upstream URL is configured
- the resolver can reach a healthy local sidecar
The application-managed local map scope is always prod, including demo mode.
Demo deployments therefore do not require a separate preloaded or area-limited
map archive.
If no local sidecar is healthy, the backend uses the hosted PMTiles upstream without logging a failed kickoff.
Background Worker
The worker receives a scope from /kickoff/{scope} and operates on:
/data/{scope}
For normal app use, the scope is prod.
Step A - Already ready
The worker checks whether planet.pmtiles points to a real versioned file with
a matching .done sentinel. If ready, it:
- checks file permissions
- ensures the low-zoom archive exists
- refreshes symlinks
- writes
status=ready
Step B - Incomplete download
If a YYYYMMDD.pmtiles file exists without a .done sentinel, the worker
resumes that exact version using the stored .url sentinel or reconstructs the
Protomaps URL from the date.
Step C - New runtime preparation
If no ready or incomplete archive exists and MAP_DOWNLOAD_URL is configured,
the worker resolves the URL, derives the build date, and prepares the archive.
MAP_DOWNLOAD_URL=latest probes recent Protomaps daily builds through
PROTOMAPS_BUILDS_BASE_URL, defaulting to https://build.protomaps.com.
Step D - Nothing to serve
If there is no archive and no MAP_DOWNLOAD_URL, the worker writes an error
status. The Java backend can still use hosted PMTiles when the local sidecar is
not selected.
Download Modes
Full planet mode
When MAP_AREA_BBOX is empty:
wget --continuedownloads the full PMTiles archive- progress is written to
/tmp/map-status.json YYYYMMDD.pmtiles.doneis written on success- low-zoom extraction starts after the full archive is available
Area-only mode
When MAP_AREA_BBOX is set:
pmtiles extract <url> YYYYMMDD.pmtiles --bbox=... --maxzoom=15- CDN range requests fetch only the requested area
- low-zoom extraction can read from the remote source URL
This mode remains available for custom local deployments, but it is no longer used for demo preloaded maps.
Tile Serving
The browser receives archive URLs like:
/mtl/api/map-proxy/prod/planet.pmtiles?mtl-map-source=public&mtl-map-archive=public-default
or, when the local sidecar is healthy:
/mtl/api/map-proxy/prod/planet.pmtiles?mtl-map-source=local&mtl-map-archive=20260404
MapTileProxyController:
- validates scope and filename
- rejects stale
mtl-map-source/mtl-map-archivecache identities - appends private
mtl-version(app container release), optionalmtl-build(image build time), andmtl-server-idupstream; missing image metadata falls back to Maven metadata - forwards the resolved client IP in
X-Forwarded-ForandX-Real-IPheaders - streams HTTP Range responses back to the browser
nginx rejects PMTiles requests without mtl-version and mtl-server-id.
The image build parameter is optional.
Browser recovery
The main map and mini-maps keep vector tiles after an individual tile error. They check a fresh PMTiles header and root directory before changing the basemap. Three failed checks, spaced by one and three seconds, enable temporary raster fallback. Each check has a five-second timeout. A healthy archive triggers a bounded tile or source retry; persistent tile errors do not cause a reload loop. Temporary raster checks the archive every 30 seconds and restores vector when available. Renderer disposal cancels pending checks. A failed main-map status probe also leaves vector enabled; an explicit download state still uses raster until preparation completes.
Volume Layout
/data/
└── prod/
├── 20260404.pmtiles
├── 20260404.pmtiles.done
├── 20260404.pmtiles.url
├── 20260404-lowzoom.pmtiles
├── 20260404-lowzoom.pmtiles.done
├── planet.pmtiles -> 20260404.pmtiles
└── world-lowzoom.pmtiles -> 20260404-lowzoom.pmtiles
Old versions are not auto-deleted to avoid accidental data loss.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
MAP_DOWNLOAD_URL |
No | empty | latest to auto-resolve the most recent Protomaps build, or a direct URL. If empty, the orchestrator serves only existing tiles. |
PROTOMAPS_BUILDS_BASE_URL |
No | https://build.protomaps.com |
Daily-build base URL used when MAP_DOWNLOAD_URL=latest. |
MAP_AREA_BBOX |
No | empty | west,south,east,north for area-only local extraction instead of full planet download. |
LOWZOOM_MAXZOOM |
No | 6 |
Maximum zoom level for the offline low-zoom extract. |
PORT |
No | 8081 |
nginx listen port. |
Internal Kickoff API
| Method | Path | Effect |
|---|---|---|
PUT |
/kickoff/prod |
Checks existing tiles, resumes incomplete work, or starts runtime preparation. |
DELETE |
/kickoff/prod |
Removes symlinks, sentinels, and tile files in /data/prod, then reprocesses. |
PUT and POST are both accepted.