apps/api/Dockerfile: multi-stage build on node:22-slim (not alpine — avoids musl-vs-glibc native binding surprises for argon2/Prisma's engine binaries; same base image family for build and runtime stages keeps "native" binaries compatible across stages). Runtime stage copies the monorepo structure as-is rather than flattening to a single package, so pnpm's symlinked node_modules stay valid. Container runs `prisma migrate deploy` on startup before starting the server, so the review environment's schema is always in sync automatically. Installs openssl explicitly in the base image: without it, Prisma can't detect the right engine binary and silently defaults to a guess that may not match what's actually on the image — caught by checking the build log, not just a successful build. apps/web/Dockerfile: builds with Vite, serves the static output via nginx (not a Node static server) — avoids the devDependency problem of needing `vite preview` in a --prod-deployed image, and is the more standard way to serve a built SPA. nginx.conf has an SPA fallback (try_files ... /index.html) ready for when client-side routing lands. docker-compose.yml: adds `api` and `web` services alongside the existing `postgres`. api's DATABASE_URL targets the `postgres` service name over the compose network (not localhost/POSTGRES_PORT, which is only the host-side mapping). Both new services require JWT_SECRET/ ports via env vars with no defaults, consistent with the project's existing no-hardcoded-credentials rule. .dockerignore added — without it, the Windows-built node_modules (with Windows-specific native binaries) would get copied into the Linux build context. Verified: full build (api + web images), `docker compose up -d` brought up all three containers, curled /health and the web root, ran a real signup through the containerized stack end-to-end.
53 lines
1.7 KiB
YAML
53 lines
1.7 KiB
YAML
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
# No defaults on purpose: POSTGRES_USER/PASSWORD/DB must be set in your
|
|
# local, git-ignored .env (see .env.example). Compose fails loudly if
|
|
# they're missing instead of falling back to a guessable credential.
|
|
POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
|
POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env}
|
|
ports:
|
|
- "${POSTGRES_PORT:-5432}:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
api:
|
|
build:
|
|
context: .
|
|
dockerfile: apps/api/Dockerfile
|
|
restart: unless-stopped
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
NODE_ENV: production
|
|
PORT: 3000
|
|
# Uses the "postgres" service name, not localhost/POSTGRES_PORT —
|
|
# container-to-container traffic stays on the compose network and
|
|
# always targets Postgres's internal port (5432).
|
|
DATABASE_URL: "postgresql://${POSTGRES_USER:?set POSTGRES_USER in .env}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/${POSTGRES_DB:?set POSTGRES_DB in .env}?schema=public"
|
|
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env}
|
|
CORS_ORIGIN: "http://localhost:${WEB_PORT:-8080}"
|
|
ports:
|
|
- "${API_PORT:-3000}:3000"
|
|
|
|
web:
|
|
build:
|
|
context: .
|
|
dockerfile: apps/web/Dockerfile
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- api
|
|
ports:
|
|
- "${WEB_PORT:-8080}:80"
|
|
|
|
volumes:
|
|
postgres_data:
|