Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
could not connect to server / timeout on the database | Tailscale not connected, or DB_HOST is the compose-internal name while you run on the host | tailscale status; check you can reach DB_HOST:DB_PORT; on the host set DB_HOST to the Tailscale IP |
| Host can reach the DB but containers cannot | Docker Desktop started before Tailscale | Restart Docker Desktop with Tailscale connected |
| Every login fails with bad credentials | EMAW_SALT does not match the database | Use the .env from the password manager; usernames are SHA256(salt + email) |
dev.emaw-app.com serves old behaviour, or alternates | The VM's stack is still running alongside yours (two tunnel connectors) | SSH to the VM, docker compose ps, docker compose down |
dev.emaw-app.com is down for everyone | Someone took the tunnel and did not hand it back, or the VM's stack crashed | SSH to the VM, docker compose up -d; check docker compose logs django |
django container restart-looping | A migration failed, or a required env var is missing | docker compose logs django; fix forward or revert; check .env |
/rest/bibles/search/ returns nothing | Qdrant volume empty (new machine or down -v) | docker compose exec django python manage.py load_bible_embeddings |
/rest/bibles/search/ is 404 | AI_SEARCH_ENABLED unset in development | Set it in .env; the routes are registered conditionally |
| Celery tasks never run on host-run Django | Redis not reachable from the host (compose does not publish it) | Add a local-only ports entry to the redis service, or run the worker inside compose |
| Caching / throttles "do nothing" | Redis down; both fail open by design | Fine for dev; check docker compose ps redis |
| Stripe webhook never arrives | Host-run Django without the tunnel, or wrong STRIPE_WEBHOOK_SECRET | Use the Stripe CLI bridge (payments/README.md) and copy its whsec_ |
| Order paid but no CRM contact / no Deposco order | Worker not running, or Deposco credentials unset (sandbox) | docker compose ps worker; deposco_check; look in error.log |
| Staff endpoint returns 403 from the Admin CRM against dev | Account not in CRM Staff and no capabilities; or CF_ACCESS_* set locally without going through Access | Add the group in Django admin; unset CF_ACCESS_TEAM_DOMAIN / CF_ACCESS_AUD locally |
| Google sign-in rejected | Client id not in GOOGLE_SIGNIN_CLIENT_IDS, or the app's origin not authorised on the client | docs/GOOGLE_SIGN_IN_SETUP.md |
| Verification / reset emails not sent | MS_* unset or the Entra secret expired | manage.py test_graph_email; renew the client secret |
| Campaign send refuses | CAMPAIGN_SENDING_ENABLED unset (correct in dev), or recipients outside CAMPAIGN_SEND_ALLOWED_GROUP_IDS | Intended. Test with group 36 only |
| R2 upload returns 503 | One of the four R2_* credentials missing | Set all four; see resources/README.md |
pytest: permission denied to create database | Running against the shared dev DB | Use the TEST_DB_* container (Testing) |
| pytest: stale schema errors | --reuse-db kept an old test DB | Add --create-db |
ImportError / wrong Django version | Using the system Python | Always .venv\Scripts\python.exe |
| Audio analysis fails on the host | ffmpeg missing | Install it, or run inside Docker where it is present |
Where to look
docker compose logs -f <service>for live output.- The
log_datavolume:app.log,error.log,security.log(rotated). On the VMs:docker compose exec django tail -n 200 /app/logs/error.log. - Every response carries
X-Request-ID; the Admin CRM shows it on error toasts. Grep the logs for it. - Django admin at
/admin/on the environment's hostname for data checks (superuser only).
See also Logs and monitoring.