services: # ── Database ──────────────────────────────────────────────────────────────── db: image: postgres:16-alpine user: "70:70" # postgres user UID:GID in alpine image (fixed by image) restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} POSTGRES_DB: ${POSTGRES_DB:-destroying_sap} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] interval: 5s timeout: 5s retries: 10 networks: - backend-net # ── Backend (management) ──────────────────────────────────────────────────── backend: build: context: ./backend dockerfile: Dockerfile network: host user: "1001:1001" restart: unless-stopped env_file: ./backend/.env environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-destroying_sap} depends_on: db: condition: service_healthy networks: - backend-net # ── Frontend (UI) ──────────────────────────────────────────────────────────── frontend: build: context: ./frontend dockerfile: Dockerfile network: host user: "1001:1001" restart: unless-stopped ports: - "80:8080" depends_on: - backend networks: - backend-net - frontend-net volumes: postgres_data: networks: # Internal-only: db ↔ backend ↔ frontend reverse proxy. No host routing. backend-net: internal: true # External-facing: only the frontend binds a host port through this network. frontend-net: