# Production compose. Pulls pre-built images from GHCR # (built and pushed by .github/workflows/deploy.yml). # # Place at /opt/gendesign/docker-compose.prod.yml on the VM. # Required env in /opt/gendesign/.env: # POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD # IMAGE_TAG (defaults to "latest") # NEXT_PUBLIC_API_BASE_URL (e.g. https://your-domain or empty for same-origin) # # Postgres + Redis run alongside the app on the same VM (Discovery mode). # Volumes are shared with docker-compose.yml so switching between files preserves data. services: postgres: image: postgis/postgis:16-3.4 restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # Bind to 127.0.0.1 only — accessible from host (for SSH tunnel) but not from public internet. # UFW additionally blocks 5432 from outside. ports: - "127.0.0.1:5432:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"] interval: 5s timeout: 5s retries: 10 redis: image: redis:7-alpine restart: unless-stopped volumes: - redis_data:/data backend: image: ghcr.io/lekss361/gendesign-backend:${IMAGE_TAG:-latest} restart: unless-stopped env_file: ./backend/.env depends_on: postgres: condition: service_healthy redis: condition: service_started ports: - "127.0.0.1:8000:8000" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 5s retries: 5 frontend: image: ghcr.io/lekss361/gendesign-frontend:${IMAGE_TAG:-latest} restart: unless-stopped environment: NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-} ports: - "127.0.0.1:3000:3000" depends_on: - backend caddy: image: caddy:2 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config depends_on: - backend - frontend volumes: postgres_data: redis_data: caddy_data: caddy_config: