name: continuous services: app: build: context: . dockerfile: Dockerfile image: ${APP_IMAGE:-continuous-app}:${APP_TAG:-local} restart: unless-stopped environment: APP_ENV: production APP_URL: ${APP_URL:-http://localhost} NODE_ENV: production PORT: "3000" HOSTNAME: 0.0.0.0 DATABASE_URL: ${DATABASE_URL:?DATABASE_URL is required} WORKER_RUN_ENABLED: ${WORKER_RUN_ENABLED:-false} # Bootstrap-only token for initial catalog seeding or host recovery. WORKER_RUN_TOKEN: ${WORKER_RUN_TOKEN:-} WORKER_OPERATOR_EMAIL: ${WORKER_OPERATOR_EMAIL:?WORKER_OPERATOR_EMAIL is required} CONTROL_PLANE_ALLOWED_TENANTS: ${CONTROL_PLANE_ALLOWED_TENANTS:-} CONTROL_PLANE_ALLOWED_WORKER_ROLES: ${CONTROL_PLANE_ALLOWED_WORKER_ROLES:-} CONTROL_PLANE_TOKENS_JSON: ${CONTROL_PLANE_TOKENS_JSON:-} CONTROL_PLANE_TOKEN_CATALOG_B64: ${CONTROL_PLANE_TOKEN_CATALOG_B64:-} depends_on: db: condition: service_healthy expose: - "3000" networks: - internal healthcheck: test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3000/health').then((r)=>process.exit(r.ok ? 0 : 1)).catch(()=>process.exit(1))\""] interval: 30s timeout: 10s retries: 5 start_period: 60s migrate: build: context: . dockerfile: Dockerfile target: migrate image: ${APP_IMAGE:-continuous-app}:${APP_TAG:-local}-migrate environment: APP_ENV: production NODE_ENV: production DATABASE_URL: ${DATABASE_URL:?DATABASE_URL is required} WORKER_RUN_ENABLED: ${WORKER_RUN_ENABLED:-false} # Bootstrap-only token for initial catalog seeding or host recovery. WORKER_RUN_TOKEN: ${WORKER_RUN_TOKEN:-} WORKER_OPERATOR_EMAIL: ${WORKER_OPERATOR_EMAIL:?WORKER_OPERATOR_EMAIL is required} CONTROL_PLANE_ALLOWED_TENANTS: ${CONTROL_PLANE_ALLOWED_TENANTS:-} CONTROL_PLANE_ALLOWED_WORKER_ROLES: ${CONTROL_PLANE_ALLOWED_WORKER_ROLES:-} CONTROL_PLANE_TOKENS_JSON: ${CONTROL_PLANE_TOKENS_JSON:-} CONTROL_PLANE_TOKEN_CATALOG_B64: ${CONTROL_PLANE_TOKEN_CATALOG_B64:-} depends_on: db: condition: service_healthy networks: - internal profiles: - tools worker-scheduler: build: context: . dockerfile: Dockerfile target: migrate image: ${APP_IMAGE:-continuous-app}:${APP_TAG:-local}-scheduler restart: unless-stopped command: ["bun", "src/worker/scheduler.ts"] environment: APP_ENV: production NODE_ENV: production DATABASE_URL: ${DATABASE_URL:?DATABASE_URL is required} WORKER_RUN_ENABLED: ${WORKER_RUN_ENABLED:-false} # Bootstrap-only token for initial catalog seeding or host recovery. WORKER_RUN_TOKEN: ${WORKER_RUN_TOKEN:-} WORKER_OPERATOR_EMAIL: ${WORKER_OPERATOR_EMAIL:?WORKER_OPERATOR_EMAIL is required} CONTROL_PLANE_ALLOWED_TENANTS: ${CONTROL_PLANE_ALLOWED_TENANTS:-} CONTROL_PLANE_ALLOWED_WORKER_ROLES: ${CONTROL_PLANE_ALLOWED_WORKER_ROLES:-} CONTROL_PLANE_TOKENS_JSON: ${CONTROL_PLANE_TOKENS_JSON:-} CONTROL_PLANE_TOKEN_CATALOG_B64: ${CONTROL_PLANE_TOKEN_CATALOG_B64:-} WORKER_SCHEDULER_ENABLED: ${WORKER_SCHEDULER_ENABLED:-false} WORKER_SCHEDULER_BASE_URL: ${WORKER_SCHEDULER_BASE_URL:-http://app:3000} WORKER_SCHEDULER_TENANT_SLUG: ${WORKER_SCHEDULER_TENANT_SLUG:-continuous-demo} WORKER_SCHEDULER_INTERVAL_MS: ${WORKER_SCHEDULER_INTERVAL_MS:-60000} WORKER_SCHEDULER_WORKFLOW_LIMIT: ${WORKER_SCHEDULER_WORKFLOW_LIMIT:-10} WORKER_SCHEDULER_ADAPTER_LIMIT: ${WORKER_SCHEDULER_ADAPTER_LIMIT:-25} WORKER_SCHEDULER_LEAD_POLL_LIMIT: ${WORKER_SCHEDULER_LEAD_POLL_LIMIT:-5} WORKER_SCHEDULER_WORKFLOW_LEASE_MS: ${WORKER_SCHEDULER_WORKFLOW_LEASE_MS:-300000} depends_on: app: condition: service_healthy networks: - internal profiles: - scheduler db: image: postgres:17-alpine restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-continuous} POSTGRES_USER: ${POSTGRES_USER:-continuous} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} volumes: - postgres_data:/var/lib/postgresql/data ports: - "127.0.0.1:${POSTGRES_PORT:-5432}:5432" networks: - internal healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 10 start_period: 20s caddy: image: caddy:2-alpine restart: unless-stopped depends_on: app: condition: service_started environment: SITE_HOSTS: ${SITE_HOSTS:?SITE_HOSTS is required} ACME_EMAIL: ${ACME_EMAIL:-admin@continuoushq.com} ports: - "80:80" - "443:443" - "443:443/udp" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - ./logs/caddy:/var/log/caddy - caddy_data:/data - caddy_config:/config networks: - internal volumes: postgres_data: caddy_data: caddy_config: networks: internal: driver: bridge