Deployment
FastGIS is deployed as a Docker Swarm stack. The primary deployment sequence is: build docs → build image → push → redeploy stack → build data packs.
1. Build the documentation
The docs site is served by FastAPI at /guide/. Build static files before the Docker image:
cd docs-site
npm ci
npm run build # → docs-site/build/
cd ..
If docs-site/build/ is absent when FastAPI starts, the /guide mount is silently skipped — the app still starts normally.
2. Build and push the Docker image
docker build -t your-registry/fastgis:latest .
docker push your-registry/fastgis:latest
:::caution No-cache rebuild
When adding new Python or JS files, rebuild with --no-cache to prevent stale layers:
docker build --no-cache -t your-registry/fastgis:latest .
:::
3. Deploy the stack
export SERVICE_NAME=fastgis
export DOMAIN=fastgis.your-domain.com
docker stack rm "$SERVICE_NAME"
sleep 10
export NODE_ID=$(docker info -f '{{.Swarm.NodeID}}')
docker node update --label-add "$SERVICE_NAME.$SERVICE_NAME-data=true" "$NODE_ID"
sleep 5
export ADMIN_API_KEY=your-strong-api-key
export ADMIN_USERNAME=admin
export ADMIN_PASSWORD=your-strong-password
export ADMIN_EMAIL=admin@your-domain.com
export SECRET_KEY=$(openssl rand -hex 32)
export APP_BASE_URL=https://$DOMAIN
docker stack deploy -c "$SERVICE_NAME.yml" "$SERVICE_NAME"
Combined one-liner
The update_service.sh script in the repo root runs all of the above steps:
sh update_service.sh fastgis your-domain.com
4. Configure regions
Edit regions.yml in the repo root to enable the countries you need, then push the change to the server (or use the API):
# Via API (preferred)
curl -X POST https://fastgis.your-domain.com/grass/data-packs/regions/enable \
-H "X-API-Key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enable": ["norway","sweden","austria","italy","spain","poland","czech-republic"]}'
5. Build data packs
Data packs are built by background jobs triggered via the API. Run them in order — each step depends on the previous one.
Step A — Curvature DB (downloads KMZ + WorldPop, ingests, enriches)
curl -X POST https://fastgis.your-domain.com/grass/data-packs/rebuild \
-H "X-API-Key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"regions": ["norway","sweden","austria","italy","spain","poland","czech-republic"],
"pack_types": ["curvature"],
"curvature_threshold": "both",
"force": true
}'
curvature_threshold accepts "300", "1000", or "both" (default). Using "both" ingests all roads from both KMZ score bands — necessary to get data for areas like western Norway where high-curvature roads score below the 1000 threshold.
Poll until status: done:
curl https://fastgis.your-domain.com/grass/data-packs/rebuild/{job_id} \
-H "X-API-Key: $ADMIN_API_KEY"
Step B — 1°×1° tile files (POI + curvature)
After Step A completes:
curl -X POST https://fastgis.your-domain.com/grass/data-packs/rebuild \
-H "X-API-Key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"regions": [],
"pack_types": ["tiles"]
}'
Tiles depend on:
- Curvature tiles:
roads.dbbuilt in Step A - POI tiles: PostGIS
osm_poitable (imported separately viaimport_osm_poi.py)
Step C — Valhalla routing tiles
Valhalla tiles require a Docker Swarm redeploy (not an API call). After enabling regions via the API:
./scripts/valhalla-rebuild.sh [stack-name]
The script downloads and builds OSM PBF tiles for all active regions in valhalla-tiles.conf (auto-updated by the regions API). Allow 45–90 minutes for a Norway → Italy build.
After the service is healthy, set VALHALLA_USE_TILES_IGNORE_PBF=True in your .env so future restarts skip the rebuild.
Full pipeline summary
| Order | Step | How | Depends on |
|---|---|---|---|
| 1 | Enable regions | POST /data-packs/regions/enable | — |
| 2 | Build curvature DB | POST /data-packs/rebuild pack_types: ["curvature"] | regions.yml, KMZ available on roadcurvature.com |
| 3 | Build tile files | POST /data-packs/rebuild pack_types: ["tiles"] | Step 2 complete |
| 4 | Build Valhalla tiles | valhalla-rebuild.sh | regions.yml updated |
| 5 | Lock Valhalla | Set VALHALLA_USE_TILES_IGNORE_PBF=True | Step 4 complete |
| 6 | Mobile downloads tiles | OfflinePacksSheet in the app | Step 3 complete |
:::note Germany and France curvature data
kml.roadcurvature.com does not currently publish KMZ files for Germany or France at the standard thresholds. The pipeline handles this gracefully — both countries are skipped with a warning and the DB is built from the remaining countries. Leave them in regions.yml; their Valhalla tiles (routing) work fine.
:::
Nginx reverse proxy
server {
listen 443 ssl;
server_name fastgis.your-domain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off; # required for SSE streaming
proxy_cache off;
proxy_read_timeout 600s;
}
}
:::caution SSE buffering
proxy_buffering off is required. Without it the terminal output in the GRASS Runner will not stream in real time.
:::
systemd (non-Docker alternative)
[Unit]
Description=FastGIS API
After=network.target
[Service]
User=fastgis
WorkingDirectory=/opt/fastgis
EnvironmentFile=/opt/fastgis/.env
ExecStart=/opt/fastgis/.venv/bin/uvicorn app.main:app \
--host 0.0.0.0 --port 8000 --workers 2
Restart=on-failure
[Install]
WantedBy=multi-user.target
Skip flags (after first build)
Set these in .env to speed up container restarts:
FETCH_REGION_DATA=false # skip KMZ + WorldPop download
VALHALLA_USE_TILES_IGNORE_PBF=True # skip Valhalla tile rebuild
REBUILD_CURVATURE_DB=false # skip curvature DB rebuild
Healthcheck
GET /healthz → {"status": "ok"}
Used as a Docker healthcheck and uptime monitor probe.