name: computer services: db: image: postgres:16.15-alpine restart: unless-stopped environment: POSTGRES_DB: computer POSTGRES_USER: computer POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} volumes: - pgdata:/var/lib/postgresql/data - ./db/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro healthcheck: test: [CMD-SHELL, pg_isready -h 127.0.0.1 -U computer -d computer] interval: 3s timeout: 3s retries: 20 networks: [backend] migrate: image: postgres:16.15-alpine restart: "no" depends_on: db: {condition: service_healthy} environment: PGPASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} DB_HOST: db DB_USER: computer DB_NAME: computer volumes: - ./db:/db:ro command: [sh, /db/migrate.sh] networks: [backend] app: build: . restart: unless-stopped depends_on: migrate: {condition: service_completed_successfully} environment: DATABASE_URL: postgres://computer:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@db:5432/computer?sslmode=disable AUTH_SECRET: ${AUTH_SECRET:?Set AUTH_SECRET in .env} NEXTAUTH_SECRET: ${AUTH_SECRET} # Optional dedicated key for stored integration credentials (review # OPS-01): rotating AUTH_SECRET no longer invalidates IMAP passwords # when ENCRYPTION_KEY is set. Back it up with the database. ENCRYPTION_KEY: ${ENCRYPTION_KEY:-} NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} ADMIN_EMAILS: ${ADMIN_EMAILS:-} COOKIE_SECURE: ${COOKIE_SECURE:-} OIDC_ISSUER: ${OIDC_ISSUER:-} OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-} OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-} ports: ["127.0.0.1:${APP_HOST_PORT:-8080}:8080"] healthcheck: test: [CMD, /computer, --healthcheck] interval: 3s timeout: 4s retries: 20 read_only: true cap_drop: [ALL] security_opt: [no-new-privileges:true] networks: [backend] web: build: ./web restart: unless-stopped depends_on: app: {condition: service_healthy} environment: API_BASE_URL: http://app:8080 AUTH_SECRET: ${AUTH_SECRET} NEXTAUTH_SECRET: ${AUTH_SECRET} NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} AUTH_TRUST_HOST: "true" OIDC_ISSUER: ${OIDC_ISSUER:-} OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-} OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-} ports: ["127.0.0.1:${WEB_HOST_PORT:-3000}:3000"] read_only: true tmpfs: [/tmp] cap_drop: [ALL] security_opt: [no-new-privileges:true] healthcheck: # busybox wget from the alpine base; /login renders without a session. test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/login"] interval: 10s timeout: 4s retries: 3 start_period: 20s networks: [backend] tests: profiles: [test] # Debian-based (not alpine): -race requires cgo, which needs gcc. # Keep the Go version in lockstep with the app Dockerfile. image: golang:1.26.1 depends_on: db: {condition: service_healthy} working_dir: /src # Mount only what `go test ./internal/...` needs — notably NOT the repo # root, so the real .env (AUTH_SECRET, POSTGRES_PASSWORD) never reaches # the test process; tests set their own secrets via t.Setenv. volumes: - ./go.mod:/src/go.mod:ro - ./go.sum:/src/go.sum:ro - ./internal:/src/internal:ro - ./db:/src/db:ro tmpfs: ["/tmp:exec"] environment: TEST_DATABASE_URL: postgres://computer:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@db:5432/computer?sslmode=disable GOCACHE: /tmp/go-cache GOMODCACHE: /tmp/go-mod command: [go, test, -race, -count=1, -v, ./internal/...] networks: [backend] # Fails when db/schema.sql drifts from the migration chain (review MIG-03). converge: profiles: [test] image: postgres:16.15-alpine restart: "no" depends_on: db: {condition: service_healthy} environment: PGPASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} DB_HOST: db DB_USER: computer volumes: - ./db:/db:ro command: [sh, /db/check-convergence.sh] networks: [backend] volumes: pgdata: name: ${POSTGRES_VOLUME:-computer_pgdata} external: ${POSTGRES_VOLUME_EXTERNAL:-false} networks: backend: driver: bridge