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} volumes: - db_data:/var/lib/postgresql/data 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" networks: default: aliases: # Unique hostname: "db" alone is ambiguous when other stacks on a # shared network (nginx_default) also expose a "db" service. - sbc-db healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d sbc"] interval: 5s timeout: 3s retries: 12 app: build: . restart: unless-stopped environment: NODE_ENV: production PORT: "8321" # Discrete variables (no URL): immune to special characters in the # password and to hostname ambiguity on shared networks PGHOST: sbc-db PGPORT: "5432" PGDATABASE: sbc PGUSER: sbc_app PGPASSWORD: ${APP_DB_PASSWORD:-sbc-app-9e12d47ab8c3f605} # Lets the app bootstrap the database itself at every startup # (role, schema, demo data) — fully idempotent, heals any volume state. PG_SUPERUSER_PASSWORD: ${POSTGRES_PASSWORD:-sbc-pg-c7f3a91d284e6b05} # 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!} # Production behind an HTTPS reverse proxy: set all three to "true". # COOKIE_SECURE adds the Secure flag on the session cookie, TRUST_PROXY # lets the app read X-Forwarded-* from the proxy, FORCE_HTTPS redirects # any plain-HTTP request to HTTPS. COOKIE_SECURE: ${COOKIE_SECURE:-false} TRUST_PROXY: ${TRUST_PROXY:-true} FORCE_HTTPS: ${FORCE_HTTPS:-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 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: