Skip to main content

Docker Compose (single-origin)

This setup keeps a single origin via a reverse proxy (Caddy) so cookie auth and CSRF work the same as production.

1Password CLI injection

Use op run to inject secrets at runtime (no secrets committed to repo).

Example with an env file:

op run --env-file env.1password -- docker compose up -d

The env reference file can reference 1Password fields directly, for example:

SESSION_SECRET=op://<vault>/<item>/session_secret
JWT_SECRET=op://<vault>/<item>/jwt_secret

See docs/platform-config/1password-cli.md for setup and troubleshooting.

Runtime DB user separation (RLS)
Use a non-superuser app role so RLS is enforced:

POSTGRES_USER=op://<vault>/<item>/POSTGRES_USER      # admin role
POSTGRES_PASSWORD=op://<vault>/<item>/POSTGRES_PASSWORD
POSTGRES_DB=op://<vault>/<item>/POSTGRES_DB
APP_DB_USER=op://<vault>/<item>/APP_DB_USER # runtime role
APP_DB_PASSWORD=op://<vault>/<item>/APP_DB_PASSWORD

Database URL (avoid localhost mistakes)

Prefer composing DATABASE_URL inside docker-compose.yml:

DATABASE_URL=postgres://\${APP_DB_USER}:\${APP_DB_PASSWORD}@db:5432/\${POSTGRES_DB}?sslmode=disable

This guarantees the service name is correct when running in Compose.

MinIO readiness

Avoid healthchecks that depend on tools like curl inside the MinIO image. Instead:

  • use depends_on: service_started for minio, and
  • add a retry loop in minio-init (which already has the mc client).

MinIO console (local only)

By default MinIO has no host port mappings (single-origin setup). For local debugging, use a compose override to expose the console, for example:

services:
minio:
ports:
- "9001:9001"

Apply it with:

docker compose -f docker-compose.yml -f docker-compose.local.yml up -d

Frontend env naming

Pick a single API base env var that matches your SvelteKit code (for example VITE_API_BASE) and remove the unused variant to avoid confusion.

For local auth flows with Secure cookies, use the local Caddy file:

docker compose -f docker-compose.yml -f docker-compose.local.yml up -d

This binds https://localhost:8443 and uses Caddyfile.local with internal TLS.

Cloudflare Tunnel With Dockerized Connector

If cloudflared runs as a Compose service, ingress targets must be Compose service names, not loopback.

  • Correct inside Docker (default stable mode): http://web:3000, http://api:8081
  • Correct inside Docker (explicit HMR mode): http://web:5173, http://api:8081
  • Incorrect inside Docker: http://127.0.0.1:5173, http://127.0.0.1:8081

Reason: in-container 127.0.0.1 is the connector container itself.

If you keep Cloudflare host routes in Zero Trust, they override local ingress entries from the mounted config file.

When switching between two local workspace copies on the same machine, set a fixed COMPOSE_PROJECT_NAME before running compose commands in both workspaces. This keeps service DNS/network naming stable so cloudflared can still resolve web and api after a workspace switch.

Frontend API Proxy Runtime Value

For containerized frontend (web service), BACKEND_BASE_URL must resolve in-container (for example http://api:8081). Avoid build-time localhost values for server-side fetch paths.

Browser-facing frontend code follows a stricter invariant:

  • Public/browser entrypoints stay on https://app-dev.evalium.me (or another public HTTPS host such as https://api-dev.evalium.me).
  • Browser requests should use same-origin /api/... paths or another public HTTPS domain.
  • Browser bundles must not default to loopback/internal origins such as http://127.0.0.1:8081, http://localhost:8081, http://api:8081, or http://web:3000.

That means BACKEND_BASE_URL=http://api:8081 is correct only for the server-side/container hop inside web; it is never a browser default.

One-shot DB init (dev only)

Run the schema reset inside a db-init helper container:

op run --env-file env.1password -- \
docker compose -f docker-compose.yml -f docker-compose.local.yml --profile local run --rm db-init

This creates the test DB (if missing) and runs backend/reset-and-reinit.sh against the Compose DB.

Common Docker commands we use

Bring everything up (local TLS + exposed db/minio ports):

op run --env-file env.1password -- \
docker compose -f docker-compose.yml -f docker-compose.local.yml --profile local up -d --build

Force a rebuild/recreate (use after backend changes):

op run --env-file env.1password -- \
docker compose -f docker-compose.yml -f docker-compose.local.yml --profile local up -d --build --force-recreate

View logs:

op run --env-file env.1password -- \
docker compose -f docker-compose.yml -f docker-compose.local.yml --profile local logs --tail=200 api

Stop and wipe volumes (destructive):

docker compose -f docker-compose.yml -f docker-compose.local.yml --profile local down -v

Run the non-destructive migrator (for prod-like flow):

op run --env-file env.1password -- \
docker compose --profile migrate run --rm db-migrate

Run the destructive reset job (safe for dev):

op run --env-file env.1password -- \
docker compose --profile reset run --rm db-reset

Run smokes against local TLS:

CURL_INSECURE=1 BASE_URL=https://localhost:8443/api/v1 \
op run --env-file env.1password -- bash backend/tests/test_all_smoke.sh