services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: vaultmcp POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env} POSTGRES_DB: vaultmcp # Bind to localhost only (not public). Override for custom local setups. ports: - "127.0.0.1:5432:5432" volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U vaultmcp"] interval: 5s timeout: 5s retries: 10 redis: image: redis:7-alpine ports: - "127.0.0.1:6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 api: build: context: . dockerfile: apps/api/Dockerfile env_file: .env environment: DATABASE_URL: postgres://vaultmcp:${POSTGRES_PASSWORD}@postgres:5432/vaultmcp REDIS_URL: redis://redis:6379 PORT: "3001" # Internal only — public traffic goes through `proxy` on :80 expose: - "3001" depends_on: postgres: condition: service_healthy redis: condition: service_healthy web: build: context: . dockerfile: apps/web/Dockerfile args: # Empty = same-origin relative API calls through the reverse proxy VITE_API_URL: ${VITE_API_URL:-} expose: - "80" depends_on: - api proxy: image: nginx:1.27-alpine ports: - "80:80" volumes: - ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - api - web volumes: pgdata: