services: caddy: image: caddy:2.10-alpine container_name: classeo_caddy environment: APP_DOMAIN: ${APP_DOMAIN} ports: - "80:80" - "443:443" volumes: - ./deploy/vps/Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config depends_on: - php - frontend restart: unless-stopped php: build: context: ./backend dockerfile: Dockerfile target: prod container_name: classeo_php environment: APP_ENV: prod APP_SECRET: ${APP_SECRET} TRUSTED_PROXIES: ${TRUSTED_PROXIES} TRUSTED_HOSTS: ${TRUSTED_HOSTS} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${MASTER_DATABASE_NAME}?serverVersion=18&charset=utf8 REDIS_URL: redis://redis:6379 MESSENGER_TRANSPORT_DSN: doctrine://default?queue_name=async MAILER_DSN: ${MAILER_DSN} ADMIN_ALERT_EMAIL: ${ADMIN_ALERT_EMAIL} DEFAULT_URI: https://${APP_DOMAIN} TENANT_BASE_DOMAIN: ${PUBLIC_BASE_DOMAIN} APP_URL: https://${APP_DOMAIN} CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN} TURNSTILE_SECRET_KEY: ${TURNSTILE_SECRET_KEY} TURNSTILE_FAIL_OPEN: ${TURNSTILE_FAIL_OPEN} JWT_SECRET_KEY: /app/config/jwt/private.pem JWT_PUBLIC_KEY: /app/config/jwt/public.pem JWT_PASSPHRASE: ${JWT_PASSPHRASE} LOCK_DSN: redis://redis:6379 SENTRY_DSN: ${SENTRY_DSN} SENTRY_ENVIRONMENT: ${SENTRY_ENVIRONMENT} TENANT_CONFIGS: >- [{"tenantId":"${TENANT_ID}","subdomain":"${TENANT_SUBDOMAIN}","databaseUrl":"postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${TENANT_DATABASE_NAME}?serverVersion=18&charset=utf8"}] volumes: - backend_uploads:/app/public/uploads - ./backend/config/jwt:/app/config/jwt depends_on: db: condition: service_healthy redis: condition: service_healthy restart: unless-stopped frontend: build: context: ./frontend dockerfile: Dockerfile target: prod container_name: classeo_frontend environment: NODE_ENV: production HOST: 0.0.0.0 PORT: 3000 ORIGIN: https://${APP_DOMAIN} PUBLIC_BASE_DOMAIN: ${PUBLIC_BASE_DOMAIN} PUBLIC_TURNSTILE_SITE_KEY: ${PUBLIC_TURNSTILE_SITE_KEY} depends_on: - php restart: unless-stopped worker: build: context: ./backend dockerfile: Dockerfile target: prod container_name: classeo_worker command: - sh - -lc - until [ -f /app/config/jwt/private.pem ]; do sleep 1; done; php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -vv environment: APP_ENV: prod APP_SECRET: ${APP_SECRET} TRUSTED_PROXIES: ${TRUSTED_PROXIES} TRUSTED_HOSTS: ${TRUSTED_HOSTS} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${MASTER_DATABASE_NAME}?serverVersion=18&charset=utf8 REDIS_URL: redis://redis:6379 MESSENGER_TRANSPORT_DSN: doctrine://default?queue_name=async MAILER_DSN: ${MAILER_DSN} ADMIN_ALERT_EMAIL: ${ADMIN_ALERT_EMAIL} DEFAULT_URI: https://${APP_DOMAIN} TENANT_BASE_DOMAIN: ${PUBLIC_BASE_DOMAIN} APP_URL: https://${APP_DOMAIN} CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN} TURNSTILE_SECRET_KEY: ${TURNSTILE_SECRET_KEY} TURNSTILE_FAIL_OPEN: ${TURNSTILE_FAIL_OPEN} JWT_SECRET_KEY: /app/config/jwt/private.pem JWT_PUBLIC_KEY: /app/config/jwt/public.pem JWT_PASSPHRASE: ${JWT_PASSPHRASE} LOCK_DSN: redis://redis:6379 SENTRY_DSN: ${SENTRY_DSN} SENTRY_ENVIRONMENT: ${SENTRY_ENVIRONMENT} TENANT_CONFIGS: >- [{"tenantId":"${TENANT_ID}","subdomain":"${TENANT_SUBDOMAIN}","databaseUrl":"postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${TENANT_DATABASE_NAME}?serverVersion=18&charset=utf8"}] volumes: - ./backend/config/jwt:/app/config/jwt depends_on: db: condition: service_healthy redis: condition: service_healthy php: condition: service_started restart: unless-stopped db: image: postgres:18.1-alpine container_name: classeo_db environment: POSTGRES_DB: ${MASTER_DATABASE_NAME} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} TENANT_DATABASE_NAME: ${TENANT_DATABASE_NAME} volumes: - postgres_data:/var/lib/postgresql/data - ./deploy/vps/postgres/01-create-tenant-db.sh:/docker-entrypoint-initdb.d/01-create-tenant-db.sh:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${MASTER_DATABASE_NAME}"] interval: 10s timeout: 5s retries: 10 start_period: 20s restart: unless-stopped redis: image: redis:7.4-alpine container_name: classeo_redis command: redis-server --save "" --appendonly no --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 10 start_period: 5s restart: unless-stopped volumes: backend_uploads: caddy_config: caddy_data: postgres_data: redis_data: