Docker Local Development & Testing¶
Run the entire application stack in Docker for local testing — backend, Postgres, frontend, Firebase auth/Firestore emulators, and (optionally) a local Ollama LLM. Nothing needs to run on your machine's terminal: Docker is the only runtime dependency, plus Node.js for running the Playwright E2E suite.
- No local Python/venv, no local Node server, no local Java/Firebase tools.
- The frontend image is built with the emulator URLs baked in, so the app on
http://localhost:3000signs in against the local auth emulator, not real Google. - The backend runs the same deterministic mock LLM used by CI (
DEEPAGENTS_MODEL=openai:test-model), so chat flows work with no API keys.
Containers¶
| Container | Built from | Host ports | Purpose |
|---|---|---|---|
ideator-postgres | postgres:16-alpine | 5433 | Postgres 16 (data in postgres_data volume) |
ideator-backend | ./backend | 8000 → 8080 | FastAPI/uvicorn; runs alembic upgrade head on start |
ideator-frontend | ./frontend | 3000 | nginx production build; proxies /api → backend |
ideator-firebase-emulators | docker/firebase-emulators.Dockerfile | 9099 (auth), 8085 (firestore), 4000 (emulator UI) | Firebase auth + Firestore emulators for project demo-companion-auth |
ideator-ollama (profile ollama) | ollama/ollama:latest | 11434 | Optional local LLM for testing the ollama provider |
The stack is defined by docker-compose.yml (tracked) plus docker-compose.override.yml, which docker compose auto-applies for local use (port/healthcheck fixes, alembic mount, emulator + Ollama services). Pipelines deploy with the plain docker-compose.yml and are unaffected.
Prerequisites¶
- Docker Desktop (on Windows: enable the WSL 2 backend).
- Node.js 24 — only needed if you run the Playwright E2E suite (section "Run the E2E tests"). Not needed for running or manually testing the stack.
- Python is not required. It is only used by one optional one-liner below to generate a Fernet key (a PowerShell alternative is provided).
Step 1 — Create the environment files¶
Root .env (gitignored)¶
Fed to the backend container and to the frontend image build. Create <repo-root>/.env:
# Deterministic mock LLM (sentinel handled by the backend test model — no real API call)
DEEPAGENTS_MODEL=openai:test-model
OPENAI_API_KEY=sk-test
OPENAI_MODEL_NAME=gpt-4
# Fernet key used to encrypt user-managed provider credentials at rest.
# Generate once and reuse (see below).
PROVIDER_CREDENTIAL_ENCRYPTION_KEY=<44-char Fernet key>
LANGGRAPH_STRICT_MSGPACK=true
# Firebase Admin — container-name hosts, because the backend runs INSIDE the compose network
FIREBASE_PROJECT_ID=demo-companion-auth
FIREBASE_AUTH_EMULATOR_HOST=firebase-emulators:9099
FIRESTORE_EMULATOR_HOST=firebase-emulators:8085
# Frontend build args (inlined into the frontend image at build time)
# Browser-side emulator URLs use the HOST-published ports, not container names:
VITE_FIREBASE_AUTH_EMULATOR_URL=http://127.0.0.1:9099
VITE_FIRESTORE_EMULATOR_HOST=127.0.0.1:8085
VITE_FIREBASE_API_KEY=fake-api-key
VITE_FIREBASE_AUTH_DOMAIN=demo-companion-auth.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=demo-companion-auth
VITE_FIREBASE_STORAGE_BUCKET=demo-companion-auth.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=000000000000
VITE_FIREBASE_APP_ID=1:000000000000:web:localtest
VITE_FIREBASE_MEASUREMENT_ID=
Generate the Fernet key (pick one):
# Python (needs the cryptography package)
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# PowerShell (any version)
$bytes = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
([Convert]::ToBase64String($bytes) -replace '\+','-' -replace '/','_')
The key only matters once you save provider credentials in the app; for plain smoke testing any valid Fernet key works.
frontend/.env.local (gitignored)¶
Used only by the Vite dev server that Playwright's E2E run boots on the host (it is never copied into the Docker image — see frontend/.dockerignore):
VITE_FIREBASE_AUTH_EMULATOR_URL=http://127.0.0.1:9099
VITE_FIRESTORE_EMULATOR_HOST=127.0.0.1:8085
VITE_FIREBASE_API_KEY=fake-api-key
VITE_FIREBASE_PROJECT_ID=demo-companion-auth
Step 2 — Start the stack¶
- First build takes a few minutes (backend image, frontend image, emulators image).
- The backend runs
alembic upgrade headbefore starting uvicorn, so the database is migrated automatically on a fresh volume. - Wait until the services report
healthy:
Expected:
ideator-postgres healthy
ideator-backend healthy
ideator-frontend healthy
ideator-firebase-emulators running
Ollama is opt-in:
Step 3 — Verify¶
curl.exe -s http://localhost:8000/api/health # backend -> 200
curl.exe -s -o /dev/null -w "%{http_code}\n" http://localhost:3000 # frontend -> 200
curl.exe -s -o /dev/null -w "%{http_code}\n" http://localhost:4000 # emulator UI -> 200
| URL | What it is |
|---|---|
| http://localhost:3000 | The app. Sign in with Google — the popup goes to the local auth emulator (127.0.0.1:9099), no real Google account needed. |
| http://localhost:8000/api/health | Backend health |
| http://localhost:9099 | Firebase auth emulator |
| http://localhost:8085 | Firestore emulator |
| http://localhost:4000 | Firebase Emulator Suite UI (inspect auth users + Firestore data) |
| http://localhost:11434 | Ollama (only with --profile ollama) |
Manual testing notes:
- The application only accepts Google sign-in (the backend rejects other providers with
unsupported_provider) — this is by design, and the emulator's fake Google credential satisfies it. - Chat runs on the deterministic mock LLM out of the box. To test a real provider, configure one under Settings → Providers in the app (API keys are stored encrypted). For a local
ollamaprovider, pull a model first:
and use endpoint http://host.docker.internal:11434 in the provider form (the backend reaches Ollama through the host bridge — not localhost, which would be the backend container itself). No API key is required for Ollama; the form hides the key field for that provider.
Run the E2E tests (Playwright)¶
One-time setup:
Run against the Docker stack:
# PowerShell
cd frontend
$env:PLAYWRIGHT_DEV_BASE_URL='http://localhost:3002'
$env:VITE_API_PROXY='http://localhost:8000'
npx playwright test --project=dev # full suite
npx playwright test --project=dev chat.spec.ts # single spec
# bash
cd frontend
PLAYWRIGHT_DEV_BASE_URL='http://localhost:3002' \
VITE_API_PROXY='http://localhost:8000' \
npx playwright test --project=dev
Why a dev server when everything else is Docker?
--project=devboots a Vite dev server for the run. Port 3000 is taken by the Docker frontend, soPLAYWRIGHT_DEV_BASE_URLpins the run to a free port (Playwright auto-picks 3001/3002 as well, but pinning is deterministic).VITE_API_PROXYrepoints the Vite/apiproxy at the host-published backend port; the Vite default target is the compose-internal hostnamebackend:8000, which only resolves inside the Docker network.- The E2E global setup signs the browser into the auth emulator through an app module (
/src/lib/firebase-emulator-testing.ts) that only exists in the Vite dev build — the production nginx image does not ship it. That is why--project=dockeris not supported. - Everything under test on the server side — backend, Postgres, and both emulators — runs in Docker; Playwright's helper frontend is the only host process, and Playwright shuts it down automatically after the run.
The suite resets application state between specs via POST /api/testing/reset and runs with a single worker (see Testing Guide for the full E2E strategy).
Reset & teardown¶
docker compose down # stop + remove containers; Postgres data volume kept
docker compose down -v # also delete the postgres_data + ollama volumes (fresh DB)
- After
down -v, the nextupmigrates the database from scratch and the emulators start empty (auth users and Firestore data are container-local and ephemeral). docker compose logs -f backend(orfrontend,firebase-emulators,ollama) streams a service's log for debugging.
Troubleshooting¶
| Symptom | Fix |
|---|---|
docker compose up blocks with postgres "Waiting" | Postgres init is still running (first start only). It resolves on its own within a minute; if it doesn't, docker start ideator-backend. |
Empty reply from server on 9099/8085/4000 from the host | Emulators must bind 0.0.0.0 inside the container. firebase.json sets "host": "0.0.0.0" for auth, firestore, and ui — if it was reverted, restore it and docker compose up -d --force-recreate firebase-emulators. |
Google sign-in popup opens accounts.google.com instead of 127.0.0.1:9099 | The frontend image was built without the emulator URLs. Check VITE_FIREBASE_AUTH_EMULATOR_URL / VITE_FIRESTORE_EMULATOR_HOST in the root .env, then docker compose up -d --build frontend. |
Backend 401s with unsupported_provider for manual curl tests | Expected: only Google sign-in is accepted. Use the app UI (or the emulator Google test credential), not raw email/password emulator sign-ups. |
| E2E fails with "Backend … did not become healthy" | The Docker stack is down or the backend is still starting — docker compose ps first. |
| E2E fails to launch a browser | cd frontend && npx playwright install. |
| Port conflict (3000/8000/5433/9099/8085/4000/11434 already in use) | Stop the other process, or remap the host port in docker-compose.override.yml (and update the matching .env / E2E env vars). |
Related¶
- Getting Started — non-Docker local setup (Python + Node)
- Testing Guide — E2E strategy, isolation, and spec authoring
- Deployment — beta/prod pipeline (uses plain
docker-compose.yml)