# ============================================================================ # Production overlay — real domains + HTTPS (Let's Encrypt) + HTTP→HTTPS # redirect + security middlewares + required secrets. # # All credentials come from the git-ignored .env NEXT TO THE COMPOSE FILES # (compose loads it automatically — keep it on the VPS, never in git): # # cp .env.example .env # then fill in STRONG values # docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build # # Requirements: DNS records for every host pointing at the server, ports # 80 + 443 open (ACME http-challenge uses port 80). The api's issuer is # derived here from AUTH_HOST (see docs/casdoor-auth.md). # # Any `${VAR:?}` below refuses to boot when the variable is missing/empty — # that is deliberate: production must not run on defaults. # ============================================================================ services: traefik: environment: # Certificates persist across restarts (acme.json in the traefik_certs volume). TRAEFIK_ACME_EMAIL: ${TRAEFIK_ACME_EMAIL:?set TRAEFIK_ACME_EMAIL in .env} postgres: environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} minio: environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env} labels: - traefik.http.routers.minio-api.rule=Host(`${S3_HOST:?set S3_HOST}`) - traefik.http.routers.minio-api.entrypoints=websecure - traefik.http.routers.minio-api.tls=true - traefik.http.routers.minio-api.tls.certresolver=letsencrypt - traefik.http.routers.minio-api.middlewares=secure-headers@file - traefik.http.routers.minio-api-redir.rule=Host(`${S3_HOST:?set S3_HOST}`) - traefik.http.routers.minio-api-redir.entrypoints=web - traefik.http.routers.minio-api-redir.middlewares=redirect-https@file - traefik.http.routers.minio-console.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`) - traefik.http.routers.minio-console.entrypoints=websecure - traefik.http.routers.minio-console.tls=true - traefik.http.routers.minio-console.tls.certresolver=letsencrypt - traefik.http.routers.minio-console.middlewares=secure-headers@file - traefik.http.routers.minio-console-redir.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`) - traefik.http.routers.minio-console-redir.entrypoints=web - traefik.http.routers.minio-console-redir.middlewares=redirect-https@file api: ports: [] # production: only reachable through Traefik environment: PROCUREMENT_DATABASE_URL: postgresql+asyncpg://postgres:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/procurement PROCUREMENT_MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env} PROCUREMENT_MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env} PROCUREMENT_CASDOOR_CLIENT_SECRET: ${CASDOOR_CLIENT_SECRET:?set CASDOOR_CLIENT_SECRET in .env} PROCUREMENT_CASDOOR_ADMIN_PASSWORD: ${CASDOOR_ADMIN_PASSWORD:?set CASDOOR_ADMIN_PASSWORD in .env} # Issuer must equal the browser-facing Casdoor origin (its discovery doc). PROCUREMENT_CASDOOR_ISSUER: https://${AUTH_HOST:?set AUTH_HOST} PROCUREMENT_CORS_ORIGINS: ${CORS_ORIGINS:?set CORS_ORIGINS in .env} PROCUREMENT_ENVIRONMENT: prod PROCUREMENT_DEV_LOGIN_ENABLED: "false" labels: - traefik.http.routers.api.rule=Host(`${API_HOST:?set API_HOST}`) - traefik.http.routers.api.entrypoints=websecure - traefik.http.routers.api.tls=true - traefik.http.routers.api.tls.certresolver=letsencrypt - traefik.http.routers.api.middlewares=secure-headers@file,api-ratelimit@file,api-bodylimit@file - traefik.http.routers.api-redir.rule=Host(`${API_HOST:?set API_HOST}`) - traefik.http.routers.api-redir.entrypoints=web - traefik.http.routers.api-redir.middlewares=redirect-https@file casdoor: ports: [] # production: only reachable through Traefik (auth host) environment: # Casdoor must know its public origin so tokens carry the right iss. origin: "https://${AUTH_HOST:?set AUTH_HOST}" labels: - traefik.http.routers.casdoor.rule=Host(`${AUTH_HOST:?set AUTH_HOST}`) - traefik.http.routers.casdoor.entrypoints=websecure - traefik.http.routers.casdoor.tls=true - traefik.http.routers.casdoor.tls.certresolver=letsencrypt - traefik.http.routers.casdoor.middlewares=secure-headers@file - traefik.http.routers.casdoor-redir.rule=Host(`${AUTH_HOST:?set AUTH_HOST}`) - traefik.http.routers.casdoor-redir.entrypoints=web - traefik.http.routers.casdoor-redir.middlewares=redirect-https@file frontend: build: args: DEV_LOGIN_ENABLED: "false" labels: - traefik.http.routers.frontend.rule=Host(`${APP_HOST:?set APP_HOST}`) - traefik.http.routers.frontend.entrypoints=websecure - traefik.http.routers.frontend.tls=true - traefik.http.routers.frontend.tls.certresolver=letsencrypt - traefik.http.routers.frontend.middlewares=secure-headers@file - traefik.http.routers.frontend-redir.rule=Host(`${APP_HOST:?set APP_HOST}`) - traefik.http.routers.frontend-redir.entrypoints=web - traefik.http.routers.frontend-redir.middlewares=redirect-https@file