Self-hosted OpenStreetMap vector tile server (tileserver-gl fork) with stateless S3/PMTiles images
999
Vector map tile server with GL styles, built from a vendored and extended fork of maptiler/tileserver-gl (based on upstream 5.7.0-pre.1). Serves OpenMapTiles-schema MBTiles/PMTiles, for example generated from OpenStreetMap / Geofabrik extracts with Planetiler, to MapLibre GL JS, Android/iOS, Leaflet and OpenLayers clients.
Source, docs, changelog and dev stack: https://github.com/tgivslife/openstreetmap-tileserver-gl
Current version: 1.0.0 · platforms linux/amd64 and linux/arm64 (one tag, the right architecture is pulled
automatically).
| Tag | Dockerfile | What it is |
|---|---|---|
1.0.0-light | Dockerfile_light | Vector only. Vector tiles, styles, fonts, sprites and TileJSON. No GL stack, small image. |
1.0.0-light-s3 | Dockerfile_light_s3 | The same light image with a config.json, styles and fonts baked in. A stateless container that reads PMTiles from S3-compatible object storage; retarget it with environment variables, no rebuild needed. |
latest
tag: pin a version.1.0.0-light-amd64, 1.0.0-light-arm64, …) are build intermediates; use the plain tags.docker inspect -f '{{json .Config.Labels}}' stsdockerhub/tileserver-gl:1.0.0-light-s3npm run images:build -- full.s3:// (AWS), s3+https:// / s3+http://
(MinIO, Ceph, R2, …) or plain https:// range reads. Private buckets use the standard AWS
credential chain (env vars, mounted ~/.aws profile, IAM role); credentials are never read from config.json.
Each range read has a deadline, so a stalled store fails fast with a 504 instead of hanging requests.Cache-Control: no-store, so a
CDN never caches an outage as blank map.${VAR} / ${VAR:-default} substitution in any config.json string, so one baked config works across
deployments; a required variable that is unset stops startup by name.?key=), propagated into every generated URL, plus an optional
Origin/Referer allowlist for hotlink deterrence. Keys are redacted from the request log, and responses under a
token are never cacheable beyond its expiry.Cache-Control on every successful response, configurable per category, with correct
ETag/304 revalidation./health endpoint used by the built-in Docker HEALTHCHECK; stays open when auth is enabled.SIGHUP without restarting the container; requests during the reload get a retryable
503 Starting, never a 404.-dark sibling.# Generate tiles first, e.g. with Planetiler:
docker run -e JAVA_TOOL_OPTIONS="-Xmx1g" -v "$(pwd)/data":/data \
ghcr.io/onthegomap/planetiler:latest --download --area=monaco
docker run --rm -it -v "$(pwd)/data":/data -p 8080:8080 \
stsdockerhub/tileserver-gl:1.0.0-light
Open http://localhost:8080/. With no config.json in /data, the server picks up the first
*.mbtiles / *.pmtiles file it finds and, for OpenMapTiles-format data, serves the bundled styles against it.
docker run --rm -it -p 8080:8080 \
-e PMTILES_URL="s3+https://minio.example.com/tiles/planet.pmtiles" \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
stsdockerhub/tileserver-gl:1.0.0-light-s3
A complete MinIO example (private bucket, SigV4) is in
tileserver-gl-dev/compose.s3.yml.
Remap the port with
-p(or setPORT), not with--port: the built-in healthcheck probes$PORT(default 8080) inside the container, so a--portchange makes the container report unhealthy.
-light-s3)osm-openmaptiles, streets-v2, basic-v2 and streets-v2-survey (survey-optimised streets), each with a -dark
variant and its own sprite sheet, localized to be served entirely by this server (no external API key). Includes 14
glyph sets (Noto Sans, Open Sans and variants).
All optional, except PMTILES_URL for -light-s3.
| Group | Variables |
|---|---|
| Server | PORT, BIND, PUBLIC_URL, NODE_ENV, UV_THREADPOOL_SIZE |
| Auth / security | TILESERVER_GL_API_KEYS, TILESERVER_GL_TOKEN_SECRET, TILESERVER_GL_TOKEN_MAX_TTL, TILESERVER_GL_ALLOWED_ORIGINS, TILESERVER_GL_ALLOWED_HOSTS |
| Metrics | TILESERVER_GL_METRICS, TILESERVER_GL_METRICS_ZOOM, METRICS_PORT |
| S3 credentials | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION, AWS_PROFILE |
| S3 client tuning | TILESERVER_GL_S3_MAX_SOCKETS, TILESERVER_GL_S3_KEEP_ALIVE, TILESERVER_GL_S3_CONNECTION_TIMEOUT_MS, TILESERVER_GL_S3_REQUEST_TIMEOUT_MS |
Baked S3 config (-light-s3) | PMTILES_URL (tileset location, required), TILE_CACHE_CONTROL (tile Cache-Control) |
Full descriptions: USAGE.md › Environment variables.
| Port | Purpose |
|---|---|
8080 | HTTP: tiles, styles, fonts, sprites, TileJSON, viewers, /health |
9090 | Prometheus /metrics (when enabled; bound to 127.0.0.1 inside the container) |
Key endpoints: /styles/{id}/style.json, /data/{id}/{z}/{x}/{y}.pbf, /fonts/{fontstack}/{range}.pbf,
/styles/{id}/ (viewer), /health.
-light-s3 reading from MinIO over S3 (Romania tileset, z8–14, k6, 0 errors):
| Setup | Throughput | Vector-tile p95 |
|---|---|---|
| 1 container, 50 users | ~640–1,270 req/s depending on the host | 354–632 ms |
| 1 container, fixed arrival rate (Apple M1 Max) | holds p95 < 200 ms up to ~740 req/s | < 200 ms |
| 4 replicas + LB, 200 users (32-core host) | ~1,275 req/s | 745 ms |
Containers are stateless and degrade by adding latency, not errors. Scale out horizontally and put a CDN in front; the cache headers are already set for it. Method and full tables: k6 README.
docker kill -s HUP <container> # reload config.json without a restart
Install · Usage & endpoints · Configuration · Deployment · Changelog
Based on TileServer GL by MapTiler and the OpenMapTiles schema. Map data © OpenStreetMap contributors (ODbL). Styles derived from MapTiler / OpenMapTiles styles. Keep the required attributions visible on any map you publish.
Content type
Image
Digest
sha256:829c2798b…
Size
176.3 MB
Last updated
11 days ago
docker pull stsdockerhub/tileserver-gl:1.0.2-light-s3