Troubleshooting¶
Port 8080 is already in use¶
Symptom: docker compose up fails with address already in use or port is already allocated.
Fix: Stop whatever is using port 8080, or change the port in .env:
Then restart the stack:
Import exits immediately or fails with a database error¶
Symptom: The import finishes in seconds (normally takes minutes), or you see a connection refused or role does not exist error.
Fix: The database must be running before you import. Start the stack first and confirm all containers are healthy:
docker compose ps # all containers should show "running" or "healthy"
# then run the importer — replace <mode> with your DEPLOY_MODE (data-node or data-node-ui)
docker compose --profile <mode> run --rm importer
If you are using the local development setup, make import is equivalent. If the database shows as unhealthy, try stopping and restarting the stack.
Map loads but shows no playgrounds¶
Symptom: Map tiles appear (streets and buildings visible) but no playground polygons are drawn, or the detail panel is empty.
Possible causes:
- Import not run — Run the importer after starting the stack. Playground data is not loaded automatically:
- Wrong relation ID — Check
OSM_RELATION_IDin.env. An incorrect ID filters out all playgrounds. Verify at nominatim.openstreetmap.org. - PBF doesn't cover the relation — The PBF extract must geographically contain the relation. If
OSM_RELATION_IDis a large region (e.g. a whole state), thePBF_URLmust point to a matching extract — a district or city PBF will silently produce 0 playgrounds. Check download.geofabrik.de for the right extract. - Docker stack not running — The app requires a live PostgREST backend. Confirm the stack is running and healthy (
docker compose ps). - Browser cache — Try a hard reload:
Ctrl+Shift+R(Windows/Linux) orCmd+Shift+R(Mac).
Geolocation button does nothing on mobile¶
Symptom: Tapping the location button on a phone browser has no effect — no movement, no error.
Cause: Browsers block the geolocation API on plain HTTP connections (including local IPs like http://192.168.1.42:8080). This is a browser security policy and cannot be overridden in the app.
Fix options:
- Test geolocation on the production HTTPS URL.
- On Android with Chrome: go to
chrome://flags, search for "Insecure origins treated as secure", add your local URL, and relaunch Chrome. - DuckDuckGo and Brave do not offer this workaround — use Chrome for local geolocation testing.
Dev server starts but changes don't appear¶
Source-clone / local dev only
This section applies when running the Vite dev server (make dev) from a source clone. Skip if using pre-built images.
Symptom: You edited a JS or CSS file, but the browser still shows the old version.
Fix: Vite hot-reload should pick up changes automatically. If it doesn't:
- Check the terminal running
make dev— a build error will prevent the browser from updating. - Try a hard reload:
Ctrl+Shift+R/Cmd+Shift+R. - If you changed
index.htmlor a file inpublic/, stop and restartmake dev.
When testing against the Docker stack, rebuild and restart the app container after changes:
LAN IP for mobile testing¶
Source-clone / local dev only
make lan-url is only available from a source clone. Skip if using pre-built images.
Run this command to find your LAN IP:
Then open http://<that-ip>:8080 on your phone.
PostgREST returns 404 or connection refused on /api/¶
Symptom: The app loads but every API call fails with "Failed to fetch" or the network tab shows 502/404 on /api/rpc/*.
Possible causes:
- PostgREST container not running — Check
docker compose ps. Thepostgrestservice should be running and healthy. - Database not ready — PostgREST starts before PostgreSQL finishes initialising on first launch. It retries automatically, but a
docker compose restart postgrestusually resolves it. - Schema cache stale — After applying schema changes, PostgREST needs a schema reload. The importer sends
NOTIFY pgrst, 'reload schema'automatically, but if that notification was missed, restart PostgREST:docker compose restart postgrest. web_anonrole missing —db/init.sqlcreates this role on first init. If you deleted and recreated thepgdatavolume without re-running init.sql (e.g. by runningdocker volume rmbut not recreating viadocker compose up), the role is missing. Rundocker compose up -dto let init.sql re-run, or run it manually.
Import fails partway through with a PBF error¶
Symptom: The importer exits with osmium or osm2pgsql reporting a corrupt or truncated file.
Fix: The cached PBF may be corrupt (interrupted download). Delete the cached files and re-run:
docker compose run --rm importer sh -c "rm -f /data/*.pbf"
docker compose --profile data-node-ui run --rm importer
The importer validates the source PBF with osmium fileinfo before using it and will re-download a corrupt file automatically. If the re-download fails, check PBF_URL in .env — make sure the URL is reachable and returns a valid PBF.
Hub shows all backends as red / unreachable¶
Symptom: The instance drawer shows every data-node with a red indicator.
Possible causes:
- CORS not configured — The Hub's browser must be able to reach each data-node's
/api/cross-origin over HTTPS. Verify with: - registry.json not updated — The Hub still has the bundled dev
registry.jsonpointing at/apiand/api2. See Federated Deployment for how to replace it. - Data-node not reachable from browser — The Hub serves the app; the Hub's browser must reach each data-node, not the Hub host. Test from a browser (not the server) by opening each data-node's
https://…/api/rpc/get_metaURL directly. - Hub cron not running — Check
federation-status.json: ifgenerated_atis older than 5 minutes, the cron job inside the hub container has stopped. Restart the hub container:docker compose --profile ui restart app.
Hub shows a backend as reachable but with 0 playgrounds¶
Symptom: A data-node appears in the Hub instance drawer with a green or yellow indicator, but its playground count is 0 and no playgrounds are drawn from that backend.
Hub operator — diagnose remotely:
If playground_count is 0 and bbox is [null,null,null,null], the database has no playground data. Pass the findings to the data-node operator.
If playground_count is > 0 and bbox contains real coordinates, the data-node is healthy. The hub has stale cached state — see Hub operator below.
Hub operator — stale cached state (playground_count > 0, valid bbox):
The hub polls each backend's get_meta at startup and every 5 minutes. If the last poll hit the backend while it was mid-import, restarting, or transiently unreachable, the hub cached bbox: null and playgroundCount: 0. With bbox: null the hub's viewport router silently excludes the backend from every map query.
-
Reload the hub page in the browser. This triggers an immediate re-poll of every backend's
get_meta. If playgrounds appear after reload, the cache was stale and self-healed. -
Check whether the hub health poller marked the backend down. The hub container runs a 60-second cron that writes
federation-status.json; backends that exceed the 3-second curl timeout are markedup: falseand the hub orchestrator skips them even whenget_metasucceeds from the browser:
curl https://your-hub.example.com/federation-status.json \
| jq '.backends | to_entries[] | select(.value.up == false) | .key'
If the backend slug appears, it is being skipped by the orchestrator. Check whether the backend is slow to respond:
Response times consistently above 3 seconds will flip up: false on the next hub poll cycle. Investigate slow PostgREST response times on the data-node side (see Map loads but shows no playgrounds for database health checks). If the latency is transient, the hub auto-recovers within 60 seconds once the backend is fast again. If the hub container is stuck showing a backend as down despite it being healthy, restart it:
Data-node operator — possible causes:
- Import not run — The stack started but the importer was never triggered:
- PBF doesn't cover the relation — See Map loads but shows no playgrounds, cause 3.
- Outdated image — See Hub backend returns "function not found" errors.
- Daemon importer sleeping after a failed/corrupt previous run — If the importer ran but was OOM-killed during preprocessing, osm2pgsql may have ingested a partial file and recorded a successful
last_import_at. The daemon will not retry until the next scheduled interval. Force an immediate one-shot reimport: Unsetting both interval variables forces one-shot mode, bypassing the grace check.
Backend shows "No position set" in instance drawer¶
Symptom: The Hub instance drawer auto-opens and a backend shows a warning: "No position set" with a link to troubleshooting docs.
Cause: The backend entry in registry.json has neither a centroid field nor a valid bbox from its get_meta response. Without position data, the backend cannot be placed on the macro view map.
Fix: Add a centroid field to the backend's entry in registry.json:
{
"instances": [
{
"slug": "your-region",
"url": "https://your-backend.example.com/api",
"name": "Your Region",
"centroid": [10.5, 51.2]
}
]
}
The centroid should be the approximate geographic center of the region in WGS84 coordinates [lon, lat]. Once the first import completes, the bbox from get_meta takes over and the centroid is only used as a fallback.
See also: registry.json Reference for the full schema.
Hub backend returns "function not found" errors¶
Symptom: The hub operator checks the data-node API and gets:
{"code":"PGRST202","message":"Could not find the function api.get_playgrounds_bbox(bbox) in the schema cache."}
The Hub shows 0 playgrounds for that backend.
Hub operator — diagnose remotely:
curl https://the-data-node.example.com/api/rpc/get_meta
# check "version" — if older than v0.4.9, the image needs updating
Data-node operator — fix:
The image predates the tiered-fetch functions added in v0.4.9. Update and re-import:
cd ~/spieli # or wherever spieli was installed
docker compose pull
docker compose --profile data-node down
docker compose --profile data-node up -d
docker compose --profile data-node run --rm importer
Hub operator — verify after the data-node operator confirms the update:
Importer fails with "permission denied" on schema apply¶
Symptom: psql reports permission denied when the importer runs api.sql.
Cause: The SQL runs as the database user configured in .env. This user needs SUPERUSER or at minimum pg_signal_backend (to terminate PostgREST connections) and CREATE on the public schema. The default user (osm) is a superuser — if you changed POSTGRES_USER, verify the role has these privileges.
Fix:
Then re-run the importer:
Database volume is very large after repeated imports¶
Symptom: The pgdata Docker volume grows beyond expectations over time.
Cause: api.sql uses DROP MATERIALIZED VIEW … CASCADE + CREATE MATERIALIZED VIEW on every apply. PostgreSQL does not reclaim the space immediately — it marks pages as dead and waits for autovacuum. After many re-imports, dead tuple bloat can be significant.
Fix: Run VACUUM FULL (briefly locks the table):
Or simply recreate the data volume after a re-import — the volume will start clean.
Hub drawer shows "updating" badge that never clears¶
Symptom: One or more backends in the Hub instance drawer permanently show an "updating" badge, even though no import is running.
Cause: The importing flag in api.import_status was left true by an importer that was killed with SIGKILL (bypasses the EXIT trap) or crashed before the trap could fire.
Self-healing: The importer clears the flag automatically at startup. Restarting the container is usually enough:
docker compose restart importer
# or, in daemon mode, the container is already running — it will clear the flag
# on its next scheduled run
Manual fix (if you need it cleared immediately without waiting for a restart):
# From the data-node host
docker compose exec db psql -U osm -d osm \
-c "UPDATE api.import_status SET importing = false WHERE id = 1;"
The Hub will pick up the corrected value on its next poll cycle (within 60 seconds).
Legal pages missing or showing wrong contact details¶
Symptom: /impressum or /datenschutz returns 404, or the pages show stale contact information after updating IMPRESSUM_* vars.
Cause: The HTML files are generated by docker-entrypoint.sh at container startup. A rebuild is required to pick up changed env vars.
Fix: Restart the app container to regenerate legal pages from current env vars:
If get_meta() still returns the old impressum_url / privacy_url, the legal URLs are baked into the database at import time. Re-run the importer to apply them:
Symptom: /impressum returns 404 despite IMPRESSUM_NAME being set.
Cause: IMPRESSUM_ADDRESS is also required. The entrypoint skips generation when either of the two required vars is empty.
Fix: Set both IMPRESSUM_NAME and IMPRESSUM_ADDRESS in .env, then make docker-build.
Symptom: PostgREST logs relation "public.playground_stats" does not exist after an upgrade.
Cause: The API_ONLY=1 importer run dropped the playground_stats materialised view but failed before recreating it, leaving the database in a broken state.
Fix: Run a full re-import to rebuild everything from scratch:
This re-applies api.sql (recreating playground_stats) and re-imports OSM data. It takes longer than API_ONLY=1 but is always safe.