services: db: image: postgres:15-alpine restart: unless-stopped environment: POSTGRES_USER: ${PGUSER:-postgres} POSTGRES_PASSWORD: ${PGPASSWORD:?PGPASSWORD is required} POSTGRES_DB: ${PGDATABASE:-medical_saas_db} MEDICAL_APP_PASSWORD: ${MEDICAL_APP_PASSWORD:?MEDICAL_APP_PASSWORD is required} volumes: - postgres_data:/var/lib/postgresql/data # Provisions the limited runtime role on first boot. - ./ops/db/init:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${PGUSER:-postgres} -d ${PGDATABASE:-medical_saas_db}"] interval: 5s timeout: 5s retries: 10 # Do NOT expose postgres to the host in production. app: build: . restart: unless-stopped depends_on: db: condition: service_healthy environment: NODE_ENV: production PORT: 3000 FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000} # The app connects as the LIMITED role so row-level security applies. DATABASE_URL: postgresql://medical_app:${MEDICAL_APP_PASSWORD:?MEDICAL_APP_PASSWORD is required}@db:5432/${PGDATABASE:-medical_saas_db}?schema=public JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required} ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required} PHI_HMAC_KEY: ${PHI_HMAC_KEY:?PHI_HMAC_KEY is required} PAYMENT_WEBHOOK_SECRET: ${PAYMENT_WEBHOOK_SECRET:-} GEMINI_API_KEY: ${GEMINI_API_KEY:-} LLM_PHI_MODE: ${LLM_PHI_MODE:-strip} TRUST_PROXY: ${TRUST_PROXY:-1} ADMIN_USERNAME: ${ADMIN_USERNAME:-} ADMIN_PASSWORD: ${ADMIN_PASSWORD:-} ports: - "3000:3000" # One-shot migration runner: docker compose run --rm migrate migrate: build: . profiles: ["tools"] depends_on: db: condition: service_healthy environment: DATABASE_URL: postgresql://${PGUSER:-postgres}:${PGPASSWORD}@db:5432/${PGDATABASE:-medical_saas_db}?schema=public command: ["./node_modules/.bin/prisma", "migrate", "deploy", "--schema", "services/data/prisma/schema.prisma"] volumes: postgres_data: