version: "3.9" name: watch-party services: # Frontend (Vite built → nginx). Only public-facing service on LAN. web: build: context: ./frontend dockerfile: Dockerfile args: PUBLIC_BASE_PATH: ${PUBLIC_BASE_PATH} image: watchparty-frontend:prod container_name: watchparty-frontend environment: BACKEND_ORIGIN: ${BACKEND_ORIGIN} ports: - "${WEB_PORT:-3000}:80" depends_on: api: condition: service_healthy restart: unless-stopped networks: [internal] healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost/"] interval: 15s timeout: 5s retries: 5 # Backend DB (internal only) db: image: postgres:16-alpine platform: ${COMPOSE_PLATFORM} environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} TZ: ${TZ} ports: - "${POSTGRES_PORT:-5432}:5432" ####### TEMPORARY EXPOSE ######### volumes: - pgdata:/var/lib/postgresql/data command: > postgres -c listen_addresses='*' -c shared_buffers=64MB -c max_connections=50 -c log_min_duration_statement=500ms -c log_destination=stderr -c logging_collector=on -c timezone='${TZ:-Asia/Tokyo}' healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s timeout: 3s retries: 20 restart: unless-stopped networks: [internal] # One-off migration job (idempotent) migrate: build: context: ./backend dockerfile: Dockerfile image: watchparty-backend:latest entrypoint: ["/app/migrate"] env_file: - ./.env depends_on: db: condition: service_healthy restart: "no" networks: [internal] # API server (internal port only; reached via web → proxy) api: image: watchparty-backend:latest env_file: - ./.env depends_on: db: condition: service_healthy migrate: condition: service_completed_successfully expose: - "8082" restart: unless-stopped networks: [internal] healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8082/healthz"] interval: 10s timeout: 5s retries: 10 ports: - "${APP_PORT:-8082}:8082" ####### TEMPORARY EXPOSE ######### networks: internal: driver: bridge volumes: pgdata: