Skip to main content

Self-hosting map tiles

The default OpenMapX light/dark styles, sprites, and layers ship with the web app. Choose where they obtain OpenMapTiles-compatible vectors and glyphs. A separate optional MapTiler full-style mode is described below.

Complete Getting started first. Local tile preparation needs a healthy data-manager, Docker, and room for the source PBF, Planetiler workspace, active archive, and staged replacement.

Enabled TileServer GL is selected automatically; explicit NEXT_PUBLIC_TILES_URL also always takes priority. Without local tiles, BASEMAP_PROVIDER=auto uses MapTiler when its effective server-side key is configured, otherwise OpenFreeMap. Choose auto, openfreemap, or maptiler in Admin → Settings → Map → Hosted Vector Basemap, or set BASEMAP_PROVIDER to override the admin value. Reload the page after an admin change. An explicit MapTiler choice without a key reports missing configuration; it does not silently switch providers. Invalid provider environment values are ignored and leave the admin setting editable. Saving a provider preserves the existing redacted MapTiler key; replace or clear that field explicitly to change the credential.

OpenFreeMap supplies OpenMapTiles-compatible vectors from its stable /planet TileJSON and keyless Noto glyphs. The OpenMapX light/dark styles and sprites stay unchanged. Its tiles/glyphs are requested directly by the browser; see the provider's privacy policy. MapTiler assets use the existing backend proxy and keep the key server-side. Neither selection changes geocoding, satellite imagery or other provider-specific key requirements.

An enabled local server is never bypassed merely because a hosted preference is set or a local request fails. Configure an explicit glyph base for external local tiles; without one, glyphs use OpenFreeMap so labels do not require a MapTiler key. The built-in TileServer GL supplies its own glyphs automatically. Its discovered /tiles paths use the web origin, independently of NEXT_PUBLIC_API_URL. For another tile-server origin (including direct local development on port 8080), set explicit tile and glyph URLs. It does not require a second catalog of server-side styles.

Martin is separate: it generates optional overlay tiles from PostGIS and does not provide the base map.

TileServer GL inputs​

The tileserver service consumes only prepared map data:

InputMounted atProduced by
tile-mbtiles/data/mbtilesopenmapx services build tileserver
tile-fonts/data/fontsopenmapx data download fonts

Its checked-in configuration deliberately has no TileServer style catalog. The web app serves /styles/openmapx-streets.json, /styles/openmapx-dark.json, and their shared sprite sheet itself.

1. Download glyphs​

pnpm openmapx data download fonts

This installs the OpenMapTiles glyph PBF tree atomically at data/tile-fonts/. Set OPENMAPTILES_FONTS_URL to use a pinned or internally mirrored font archive. The download is needed only when TileServer GL or offline package generation uses the self-hosted OpenMapX dataset.

2. Build vector tiles​

Download an OSM extract and build the OpenMapTiles-schema MBTiles archive:

pnpm openmapx data download osm europe/germany
pnpm openmapx services build tileserver --region europe/germany

The equivalent data alias is:

pnpm openmapx data build tileserver europe/germany

Planetiler writes data/tile-mbtiles/tiles.mbtiles. It builds into data/tile-mbtiles.next/ and only replaces the archive once the build succeeds, so a failed build keeps the old tiles. Its JVM heap defaults to half the PBF size (at least 2 GB; -Xmx30g for the planet), and its downloaded sources and temp files live in data/planetiler/, where later builds reuse the sources. Set PLANETILER_JAVA_TOOL_OPTIONS or PLANETILER_WORK_DIR in .env to change either. To build on a bigger machine and serve from this one, see Building on another host.

Stop TileServer GL before replacing a running archive:

pnpm openmapx services stop tileserver
pnpm openmapx services build tileserver --region europe/germany
pnpm openmapx services enable tileserver
pnpm openmapx compose render
pnpm openmapx data link
pnpm openmapx services start tileserver

data link hardlinks the two producer trees into the read-only consumer paths. With the default local binding, verify the TileJSON, a tile, and a glyph:

curl -sf http://localhost:8080/data/openmapx.json
curl -sf -o /dev/null http://localhost:8080/data/openmapx/0/0/0.pbf
curl -sf -o /dev/null 'http://localhost:8080/fonts/Noto%20Sans%20Regular/0-255.pbf'

4. Configure the web app​

For the built-in TileServer GL, automatic discovery supplies these URLs. Set explicit values only for a different origin or a deliberate override:

# infra/docker/.env
NEXT_PUBLIC_STYLE_PROVIDER=openmapx
NEXT_PUBLIC_TILES_URL=https://maps.example.com/tiles/data/openmapx.json
NEXT_PUBLIC_MAP_STYLE_URL=https://maps.example.com/tiles

NEXT_PUBLIC_TILES_URL replaces only the bundled style's openmaptiles vector source. NEXT_PUBLIC_MAP_STYLE_URL supplies /fonts; despite its historical name, it is not a style JSON endpoint. The sprites remain same-origin web-app assets. Offline package preparation, polling, archives, and glyph downloads use NEXT_PUBLIC_API_URL as well; this matters in local development where the web app and API normally use different origins. Apply changed container values with pnpm openmapx services start app-web, then reload the page. An in-place restart does not reload environment values.

NEXT_PUBLIC_STYLE_PROVIDER=maptiler selects MapTiler's complete hosted style through the API only when MapTiler is the resolved vector provider. Self-hosted and OpenFreeMap vectors always use the owned styles. Complete hosted styles do not use the local OpenMapX offline-package pipeline.

Relief and contours​

The Terrain option keeps the active vector style, adding multidirectional hillshade and contour lines underneath its roads and labels. It also enables MapLibre's elevation surface, so a pitched map shows 3D terrain. With self-hosted vector tiles, Terrain uses Mapterhorn's Terrarium DEM through the OpenMapX API proxy and generates contours in a browser worker. No MapTiler key is needed for this terrain source. The visible map credit links to Mapterhorn's full source attribution, which includes the underlying elevation providers. The browser contacts OpenMapX for DEM tiles, not Mapterhorn directly. Mapterhorn publishes the tile endpoint for interactive maps and a source-by-source license and attribution catalog. Its terrain data is assembled from open-data sources with attribution requirements; the Mapterhorn link in the map footer is the project's consolidated upstream credit. The contour renderer, maplibre-contour, is BSD-3 licensed and is bundled with the web app. For a custom DEM, operators must supply the correct credit and verify that dataset's terms separately. Mapterhorn documents public tile access but does not publish a service-level capacity or availability commitment; high-volume deployments can serve its downloadable PMTiles themselves, subject to the same source attributions.

With a MapTiler basemap, Terrain keeps MapTiler Terrain RGB and MapTiler Contours through the existing API proxy; MAPTILER_KEY is required. MapTiler's terrain datasets have a native maximum zoom of 14. Mapterhorn's worldwide DEM supports contours through zoom 12; higher-resolution elevation tiles are available in some regions, and closer views overzoom where necessary.

The Default street map uses the same DEM for soft mountain shading: a plain hillshade from zoom 6 that fades out by zoom 14, with no contours, elevation tint or 3D terrain. It requests DEM tiles only up to zoom 9 and overzooms them, which keeps the shading generalised and the tile count low. Satellite and Cycling load no elevation data.

A deployment with its own DEM can set NEXT_PUBLIC_TERRAIN_DEM_TILEJSON_URL to a locally served DEM TileJSON endpoint and set NEXT_PUBLIC_TERRAIN_DEM_ENCODING to mapbox (default) or terrarium. For browser-generated contours, the app reads the tile URL from TileJSON. You can set NEXT_PUBLIC_TERRAIN_DEM_TILE_URL_TEMPLATE to its local {z}/{x}/{y} tile URL to skip that extra TileJSON request. Alternatively, set NEXT_PUBLIC_TERRAIN_CONTOUR_TILEJSON_URL to a vector TileJSON endpoint with a contour source layer and height/nth_line fields. Set NEXT_PUBLIC_TERRAIN_ATTRIBUTION_NAME and NEXT_PUBLIC_TERRAIN_ATTRIBUTION_URL to the data provider's required credit.

The web runtime reads NEXT_PUBLIC_TERRAIN_DEM_TILE_URL_TEMPLATE and NEXT_PUBLIC_TERRAIN_DEM_ENCODING, but the current app-web service manifest does not forward those two variables. Setting them only in the managed .env therefore does not apply them to that container. Verify the effective web container environment before relying on a custom encoding/template; the forwarded TileJSON and attribution settings remain available. A custom manifest or non-Compose runtime must supply the missing variables explicitly.

Downloaded browser packages currently contain OpenMapTiles vectors and glyphs, but no elevation or contour tiles. While an offline package is active, Default and Terrain therefore show the flat vector style and make no terrain tile requests.

Offline packages​

Offline package generation reads the same tiles.mbtiles and tile-fonts trees. A browser package consists of:

  • one immutable, checksummed PMTiles archive for the selected bounds and zooms;
  • versioned glyph PBFs needed by the bundled OpenMapX light/dark styles; and
  • a small manifest describing coverage, checksums, schema, attribution, and the glyph namespace.

Style JSON and sprites are not copied into each package. The service worker installs the same bundled styles and sprites used online, and the offline MapLibre style rewrite changes only the vector source and glyph template. With multiple downloaded areas, MapLibre still receives one openmaptiles source and one layer set, so overlapping packages cannot duplicate labels or fills.

See Offline maps and navigation for browser storage, validation, and navigation-continuation behavior.

Updating data​

To refresh glyphs or the regional tile archive:

pnpm openmapx data download fonts
pnpm openmapx data download osm europe/germany
pnpm openmapx services stop tileserver
pnpm openmapx services build tileserver --region europe/germany
pnpm openmapx data link
pnpm openmapx services start tileserver

openmapx data update includes glyphs but builds every installed buildable engine, including unselected ones; use targeted commands for a tile-only refresh. Existing browser packages remain immutable; preparing the same area against refreshed source data produces a new content-addressed identity.

Martin overlays​

Martin serves PostGIS tile functions as dynamic vector tile endpoints. It serves only the sources declared in services/martin/config/martin.yaml, the traffic-flow overlay's segment_flow, and logs in as openmapx_tiles, a role that can call the functions in the tiles schema and read no table. Set its password before enabling Martin; PostGIS applies it on its next start:

# Generate a value, then set MARTIN_DB_PASSWORD once in infra/docker/.env:
openssl rand -hex 32
# In a planned database maintenance window:
pnpm openmapx services start postgis
pnpm openmapx services enable martin
pnpm openmapx compose render
pnpm openmapx services start martin
curl -sf http://localhost:3002/health

Martin is independent of TileServer GL. Most deployments that only need a self-hosted base map need TileServer GL, not Martin.

Hosted source and attribution​

OpenFreeMap uses the OpenMapTiles schema and needs no API key. Its public service has no SLA; operators can select another hosted source or their own tiles. The footer credits OpenStreetMap and OpenMapTiles, plus the selected hosted provider. Local/offline tiles do not display a hosted provider credit. OpenFreeMap and self-hosted basemaps default to proxied Mapterhorn terrain and generated contours, keeping Terrain keyless too.

If the public configuration API is unavailable or returns invalid configuration, the page preserves explicit local URLs or tries the conventional /tiles paths. It does not silently switch a potentially self-hosted instance to a third party. A hosted deployment can consequently show an unavailable map during an API outage; reload after the API recovers to retry provider discovery.