Skip to main content

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 ..
info

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.db built in Step A
  • POI tiles: PostGIS osm_poi table (imported separately via import_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

OrderStepHowDepends on
1Enable regionsPOST /data-packs/regions/enable
2Build curvature DBPOST /data-packs/rebuild pack_types: ["curvature"]regions.yml, KMZ available on roadcurvature.com
3Build tile filesPOST /data-packs/rebuild pack_types: ["tiles"]Step 2 complete
4Build Valhalla tilesvalhalla-rebuild.shregions.yml updated
5Lock ValhallaSet VALHALLA_USE_TILES_IGNORE_PBF=TrueStep 4 complete
6Mobile downloads tilesOfflinePacksSheet in the appStep 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.