services: wedding-db: image: postgres:17-alpine container_name: wedding-db restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-wedding} POSTGRES_USER: ${POSTGRES_USER:-wedding} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-wedding} volumes: - ./wedding-db-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-wedding} -d ${POSTGRES_DB:-wedding}"] interval: 5s timeout: 5s retries: 10 networks: - wedding-private wedding-minio: image: minio/minio:latest container_name: wedding-minio restart: unless-stopped command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} volumes: - ./wedding-minio-data:/data labels: - traefik.enable=true - traefik.docker.network=${TRAEFIK_NETWORK:-traefik} - traefik.http.routers.wedding-minio-api.rule=Host(`${WEDDING_MINIO_API_HOST:-wedding-minio.localhost}`) - traefik.http.routers.wedding-minio-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure} - traefik.http.routers.wedding-minio-api.tls=true - traefik.http.routers.wedding-minio-api.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt} - traefik.http.routers.wedding-minio-api.service=wedding-minio-api - traefik.http.services.wedding-minio-api.loadbalancer.server.port=9000 - traefik.http.routers.wedding-minio-console.rule=Host(`${WEDDING_MINIO_CONSOLE_HOST:-wedding-minio-console.localhost}`) - traefik.http.routers.wedding-minio-console.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure} - traefik.http.routers.wedding-minio-console.tls=true - traefik.http.routers.wedding-minio-console.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt} - traefik.http.routers.wedding-minio-console.service=wedding-minio-console - traefik.http.services.wedding-minio-console.loadbalancer.server.port=9001 networks: - wedding-private - traefik-public wedding-minio-init: image: minio/mc:latest container_name: wedding-minio-init depends_on: - wedding-minio environment: S3_BUCKET: ${S3_BUCKET:-wedding-uploads} MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} volumes: - ./deployment/minio-cors.json:/etc/minio/cors.json:ro entrypoint: > /bin/sh -c " until mc alias set local http://wedding-minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD; do echo 'Waiting for MinIO...'; sleep 2; done; mc mb --ignore-existing local/$$S3_BUCKET && mc anonymous set none local/$$S3_BUCKET && mc cors set local/$$S3_BUCKET /etc/minio/cors.json " networks: - wedding-private wedding-migrate: build: context: . dockerfile: Dockerfile image: wedding-website:latest container_name: wedding-migrate restart: "no" command: ["node", "scripts/migrate-sql.mjs"] depends_on: wedding-db: condition: service_healthy environment: DATABASE_URL: ${DATABASE_URL:-postgres://wedding:wedding@wedding-db:5432/wedding} networks: - wedding-private wedding-app: build: context: . dockerfile: Dockerfile image: wedding-website:latest container_name: wedding-app restart: unless-stopped depends_on: wedding-db: condition: service_healthy wedding-minio-init: condition: service_completed_successfully wedding-migrate: condition: service_completed_successfully environment: NODE_ENV: production PORT: 3000 HOSTNAME: 0.0.0.0 DATABASE_URL: ${DATABASE_URL:-postgres://wedding:wedding@wedding-db:5432/wedding} AUTH_SECRET: ${AUTH_SECRET} AUTH_GOOGLE_ID: ${AUTH_GOOGLE_ID} AUTH_GOOGLE_SECRET: ${AUTH_GOOGLE_SECRET} ADMIN_EMAILS: ${ADMIN_EMAILS} APP_URL: ${APP_URL:-https://wedding.localhost} AUTH_URL: ${AUTH_URL:-https://wedding.localhost} RSVP_DEADLINE: ${RSVP_DEADLINE} S3_ENDPOINT: ${S3_ENDPOINT:-http://wedding-minio:9000} S3_REGION: ${S3_REGION:-us-east-1} S3_BUCKET: ${S3_BUCKET:-wedding-uploads} S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-minioadmin} S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-minioadmin} S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true} labels: - traefik.enable=true - traefik.docker.network=${TRAEFIK_NETWORK:-traefik} - traefik.http.routers.wedding-app.rule=Host(`${WEDDING_APP_HOST:-wedding.localhost}`) - traefik.http.routers.wedding-app.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure} - traefik.http.routers.wedding-app.tls=true - traefik.http.routers.wedding-app.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt} - traefik.http.services.wedding-app.loadbalancer.server.port=3000 networks: - wedding-private - traefik-public networks: wedding-private: name: wedding-private traefik-public: name: ${TRAEFIK_NETWORK:-traefik} external: true