name: sluc-business-club services: db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: sbc POSTGRES_USER: postgres # Preconfigured defaults so the stack deploys without any .env # (Portainer, etc.). Override them in production. POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-sbc-pg-c7f3a91d284e6b05} APP_DB_PASSWORD: ${APP_DB_PASSWORD:-sbc-app-9e12d47ab8c3f605} volumes: - db_data:/var/lib/postgresql/data - ./db/init:/docker-entrypoint-initdb.d:ro ports: # Loopback only: reachable from the host machine (psql, backups), # never from the network. Remove this mapping to close it entirely. - "127.0.0.1:${DB_PORT:-58412}:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d sbc"] interval: 5s timeout: 3s retries: 12 # One-shot at every stack start: realigns the sbc_app role password with # APP_DB_PASSWORD, healing volumes initialized with an older value. db-sync: image: postgres:16-alpine restart: "no" depends_on: db: condition: service_healthy environment: PGHOST: db PGUSER: postgres PGDATABASE: sbc PGPASSWORD: ${POSTGRES_PASSWORD:-sbc-pg-c7f3a91d284e6b05} APP_DB_PASSWORD: ${APP_DB_PASSWORD:-sbc-app-9e12d47ab8c3f605} volumes: - ./db/sync-app-role.sh:/sync-app-role.sh:ro entrypoint: ["/bin/sh", "/sync-app-role.sh"] app: build: . restart: unless-stopped environment: NODE_ENV: production PORT: "8321" DATABASE_URL: postgres://sbc_app:${APP_DB_PASSWORD:-sbc-app-9e12d47ab8c3f605}@db:5432/sbc # Empty by default: the app then generates a random ephemeral secret at # startup. Set a fixed value to keep sessions across restarts. JWT_SECRET: ${JWT_SECRET:-} ADMIN_INITIAL_PASSWORD: ${ADMIN_INITIAL_PASSWORD:-SlucAdmin2026!} MEMBER_INITIAL_PASSWORD: ${MEMBER_INITIAL_PASSWORD:-SlucMembre2026!} COOKIE_SECURE: ${COOKIE_SECURE:-false} TRUST_PROXY: ${TRUST_PROXY:-false} UPLOAD_DIR: /data/uploads volumes: - uploads:/data/uploads ports: - "${APP_PORT:-8321}:8321" networks: default: proxy: aliases: # Stable name for the nginx upstream: proxy_pass http://sbc-app:8321; - sbc-app depends_on: db: condition: service_healthy db-sync: condition: service_completed_successfully read_only: true tmpfs: - /tmp security_opt: - no-new-privileges:true healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8321/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] interval: 10s timeout: 5s retries: 6 networks: default: # Existing network of the nginx reverse-proxy stack. Create it first if it # does not exist: docker network create nginx_default proxy: external: true name: ${PROXY_NETWORK:-nginx_default} volumes: db_data: uploads: