Skip to main content

Architecture

Dawarich Atlas is built around eight design principles:

  1. Local-first. Every layer runs on your hardware. No outbound API calls at runtime.
  2. Open data only. OSM, SRTM, GTFS — all freely downloadable.
  3. MapLibre GL as the renderer. Vector-first.
  4. PMTiles as the tile format. Static files, no tile server process.
  5. Minimal overlap, minimal resource consumption. Each service owns a distinct query type.
  6. Single compose file. One compose.yml, bind mounts under ./data/, region selected via .env.
  7. Caddy as the edge. Auto-HTTPS-ready, range requests + compression out of the box.
  8. One Rails codebase + one Go sidecar. The Rails layer is the public surface (API + admin UI + Turbo Streams). A small Go service (atlas-control) owns the Docker socket so Rails never has to.

Topology

┌────────────────────────┐
│ Browser (MapLibre) │
└───────────┬────────────┘


┌──────────────┐
│ Caddy │ TLS + /tiles/* static
└───┬────────┬─┘
│ │
/tiles/* │ │ /*
▼ ▼
┌────────┐ ┌──────────────────┐ ┌──────────────────┐
│ PMTiles│ │ Dawarich Atlas │ ──HTTP→ │ atlas-control │
│ static │ │ (Rails: API + │ │ (Go sidecar, │
│ │ │ admin UI) │ │ docker socket) │
└────────┘ └────────┬─────────┘ └──────────────────┘

fan-out (internal Docker network only):

┌────────┬─────────────┬────────────┼────────────┬────────┐
▼ ▼ ▼ ▼ ▼ ▼
photon placeholder libpostal valhalla overpass otp

Only Caddy is published on host port 8484. Every other service is reachable only on the internal Docker network.

Layers

#LayerComponentData source
1Base map tilesProtomaps PMTilesOSM planet (Protomaps daily build or self-built via Planetiler)
2Tile servingCaddy static + range + CORSn/a
3Map styleprotomaps-themes-base (5 themes)github.com/protomaps/basemaps
4GeocodingPhoton + Placeholder + libpostalOSM + Who's on First
5RoutingValhallaOSM PBF + SRTM DEM
6ElevationBundled with ValhallaSRTM 1-arcsec
7Terrain / hillshade (planned)Terrain-RGB raster tilesSRTM → rio-rgbify → PMTiles
8POI lookupOverpass API self-hostedOSM PBF
9TransitOpenTripPlanner 2GTFS + OSM

Why these engines

PickReasoning
Photon over Nominatim~70 GB prebuilt index vs Nominatim's ~1 TB Postgres. Same OSM source, much smaller box.
PlaceholderResolves admin-hierarchy (country / state / city) when Photon's OSM tags are thin.
libpostalStatistical address parser. Normalises long structured queries before they hit Photon.
Valhalla over OSRMMultimodal in one binary, built-in elevation, MIT license, ferry/toll/highway avoidance.
OverpassTag-aware queries over OSM. Self-updates from minute diffs.
OpenTripPlanner 2The reference open-source multimodal planner. GTFS + OSM in a single graph.
MapLibre GL JSThe open fork of mapbox-gl. Reads PMTiles natively.
CaddyAuto-HTTPS, range requests + compression for *.pmtiles, ergonomic config.

App layer

Dawarich Atlas's job is to orchestrate: it exposes a clean /api/v1/* surface, fans out to the right engines, normalises shape, and degrades gracefully when an upstream is down. The internal client classes are thin:

  • PhotonClient, ValhallaClient, OverpassClient, OtpClient — direct HTTP wrappers.
  • SearchOrchestrator, ReverseOrchestrator, BatchReverseGeocoder — composition (Photon + Placeholder + cache + grid-snap).
  • ControlPlaneClient — talks to the atlas-control Go sidecar that owns docker socket access.

The map UI is Hotwire (Turbo + Stimulus) with a MapLibre Stimulus controller. Admin operations stream over Action Cable / Turbo Streams.

The atlas-control Go sidecar

Atlas never touches the Docker socket directly. A separate Go service — atlas-control — owns that responsibility and exposes a small HTTP API the Rails layer calls into. The split exists for three reasons:

  • Privilege boundary. The Rails container ships without /var/run/docker.sock mounted. Only atlas-control has the socket. If the Rails app is ever compromised, the attacker still can't spawn or stop containers directly.
  • Operational surface. atlas-control is the thing that boots / stops profiles (docker compose --profile geocoding up -d), tails service logs, downloads region PBFs, fetches PMTiles basemaps, and reports status back over a streaming endpoint.
  • Independent release cycle. The sidecar ships as its own container image. Bumping the sidecar doesn't require redeploying Atlas, and vice versa.

What flows through the sidecar:

Triggered from RailsSidecar action
POST /admin/services/:namedocker compose --profile <X> up -d <name> or … down <name>
GET /admin/services/:name/logstail container logs, stream over HTTP
POST /admin/applyapply region change + start projected service set
POST /admin/regionsswap regional bind-mount data + signal dependent services
POST /admin/tiles/downloadpull a PMTiles file (Protomaps daily, custom URL, etc.) into data/tiles/
GET /admin/tilesreport state of the local basemap file (exists, size, freshness)

The sidecar source lives at atlas-control/ in the main repo. It uses the Docker Engine API directly (no shelling out to docker compose); a small embedded YAML parser reads the same compose.yml Caddy + Atlas already use.