Troubleshooting¶
"connection refused" hitting localhost:8080 right after docker compose up¶
Expected during the first ~15-30 seconds — server waits for postgres's
healthcheck (depends_on: condition: service_healthy) before it even
starts, and then runs alembic upgrade head before uvicorn binds the port.
docker compose ps # postgres should show "healthy"; server "Up"
docker compose logs -f server # watch for "Running database migrations..." then "Uvicorn running on..."
If it's still refusing connections after a minute, check docker compose
logs server for a migration failure (see below) rather than assuming it's
still starting.
alembic upgrade head fails at boot¶
Server logs will show the alembic traceback before the container exits. Common causes:
- Database not reachable —
RAI_DATABASE_URLdoesn't match thepostgresservice's actual credentials/host. If you changedPOSTGRES_PASSWORDin an override file,RAI_DATABASE_URLmust match. - Partial/corrupted schema from a previous failed migration — inspect
alembic_versiontable: Compare againstpackages/raise-server/alembic/versions/head. Resolving a stuck migration is case-by-case (out of scope for a generic SOP) — the safe fallback is restoring from a known-good backup (backup-restore.md) rather than hand-editing the version table.
Port already in use (8080 or 5432)¶
Something else on the host is bound to that port.
Either stop the conflicting process, or remap the host side in a
docker-compose.override.yml (leave the container-side port alone):
GET /health returns "database": "disconnected"¶
The server process is up but can't reach PostgreSQL — check:
docker compose ps postgres # is it running and healthy?
docker compose logs postgres # any crash-loop or OOM in its logs?
docker compose exec postgres pg_isready -U rai -d rai
If postgres itself is healthy but the server still reports disconnected,
verify RAI_DATABASE_URL in the server service's environment matches the
postgres service's actual POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB
— a mismatch here is the most common cause after a manual override.
Export endpoints return 401¶
/api/v2/export/* require a valid API key resolving to an authenticated
member (same as GET /api/v2/metrics) — this is not specific to
self-hosted, see export-api-usage.md. Check the
Authorization: Bearer <key> header is present and the key hasn't been
revoked (docker compose exec postgres psql -U rai -d rai -c "SELECT
key_prefix, is_active FROM api_keys;").
Export endpoint returns 400 with "malformed cursor"¶
The ?cursor= value was hand-edited, truncated, or copied from a different
endpoint's response. Cursors are opaque tokens — never construct or edit
one; always use the exact next_cursor value from the previous page's
response, or omit cursor entirely to start from page one.
Resetting all data¶
Destructive — deletes the PostgreSQL volume entirely, including anything not backed up:
docker compose down -v
docker compose up -d --build
docker compose exec -T postgres psql -U rai -d rai \
< packages/raise-server/scripts/seed_dev.sql
Where to look next¶
- Server application logs:
docker compose logs server - PostgreSQL logs:
docker compose logs postgres - Live shell in the server container:
docker compose exec server bash - Live
psqlsession:docker compose exec postgres psql -U rai -d rai
If none of the above resolves it, capture docker compose logs output (both
services) and the exact command that triggered the failure before filing an
issue — self-hosted support requests without logs are rarely actionable.