# No `version:` key — it is obsolete in Compose v2 and only prints a warning on # every command, which made the delivery checklist's output noisy. services: db: image: docker.arvancloud.ir/postgres:15-alpine container_name: goldbank_db environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} POSTGRES_DB: ${POSTGRES_DB:-goldbank} ports: - "${EXTERNAL_DB_PORT:-5432}:5432" volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-goldbank}"] interval: 5s timeout: 5s retries: 12 api: build: context: ./apps/api dockerfile: Dockerfile container_name: goldbank_api # Prisma connects on module init, so a not-yet-ready DB crash-loops the API. depends_on: db: condition: service_healthy environment: - DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@db:5432/goldbank?schema=public} - PORT=${PORT:-3000} # Auth. Without these the API falls back to a well-known development # secret, so set them in the deployment environment. - JWT_SECRET=${JWT_SECRET:-} - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-} - JWT_CUSTOMER_SECRET=${JWT_CUSTOMER_SECRET:-} - JWT_CUSTOMER_EXPIRES_IN=${JWT_CUSTOMER_EXPIRES_IN:-} # ⚠ DELIVERY ONLY — a fixed OTP that signs in ANY active customer. # Empty/unset disables it, which is the correct state for production. # See ISSUES REL-01. Must be exactly 4 digits to be typable in the apps. - OTP_MASTER_CODE=${OTP_MASTER_CODE:-} # SMS. All blank = the "relay" gateway with its built-in defaults (D26). # A failed SMS never blocks the boot or an operation (D20). - SMS_PROVIDER=${SMS_PROVIDER:-} - SMS_RELAY_URL=${SMS_RELAY_URL:-} - SMS_RELAY_SECRET=${SMS_RELAY_SECRET:-} - SMS_RELAY_HOST=${SMS_RELAY_HOST:-} - SMS_RELAY_PLATFORM=${SMS_RELAY_PLATFORM:-} - SMS_TREZ_URL=${SMS_TREZ_URL:-} - SMS_TREZ_USERNAME=${SMS_TREZ_USERNAME:-} - SMS_TREZ_PASSWORD=${SMS_TREZ_PASSWORD:-} - SMS_TREZ_SENDER_LINE=${SMS_TREZ_SENDER_LINE:-} # Accounting integration (D27): encrypts the token stored from the admin # panel. Blank falls back to a JWT_SECRET-derived key with a warning. - ACCOUNTING_SECRET_KEY=${ACCOUNTING_SECRET_KEY:-} - ACCOUNTING_WORKER=${ACCOUNTING_WORKER:-} # Admin mobiles told when a customer completes their profile (D22). # Blank = nobody is notified; the upload still succeeds. - PROFILE_ADMIN_MOBILES=${PROFILE_ADMIN_MOBILES:-} # No host ports — Dokploy/Traefik routes by domain to container port 3000 expose: - "3000" # Uploaded files (product images, etc.) land in storage/uploads inside the # container. Without a volume they vanish on every recreate/redeploy, and # every UploadedFile/ProductImage row created before that would then point # at a URL that 404s. volumes: - api_uploads:/app/storage/uploads restart: unless-stopped # The bare name "api" is ambiguous on Dokploy's shared network (other stacks # register it too), which made nginx dial the wrong containers. Use a unique alias. networks: default: aliases: - goldbank-api admin-panel: build: context: ./apps/admin-panel dockerfile: Dockerfile container_name: goldbank_admin_panel # No host ports — Dokploy/Traefik routes by domain to container port 80 expose: - "80" restart: unless-stopped depends_on: - api user-app: build: context: ./apps/user-app dockerfile: Dockerfile container_name: goldbank_user_app # No host ports — Dokploy/Traefik routes by domain to container port 80 expose: - "80" restart: unless-stopped depends_on: - api # Same copy-only nginx pattern as user-app; map a separate Dokploy domain to this service. user-app-v2: build: context: ./apps/user-app-v2 dockerfile: Dockerfile container_name: goldbank_user_app_v2 expose: - "80" restart: unless-stopped depends_on: - api volumes: postgres_data: api_uploads: