Ctrl K
ComunidadGuías…

docker-compose.acceptance.yml

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# DOMINUS — Cloud acceptance stack.
#
# The production-parity stack for the Cloud edition: PostgreSQL with
# row-level security, Redis, and the three long-running processes (api,
# worker, scheduler) against the same database, with the same tenant
# isolation the production deployment has.
#
# It is NOT the production overlay. The monitoring stack, the TLS
# terminator, the digest-pinned images, the fail2ban/hostssl posture and
# the backups are covered by deploy/terraform and by the docker gate. What
# this file adds is the evidence no other gate can produce: the same
# schema, the same RLS policies, the same job queue, with two tenants in
# it — before a paid deployment exists.
#
# The images are BUILT from the working tree, not pulled: the point of the
# suite is to test the code that is about to be released, not the last
# published tag. For a released candidate, pin the tag in
# scripts/acceptance/cloud-acceptance.mjs (ACCEPTANCE_IMAGE_TAG).
#
# Usage:
#   npm run acceptance:cloud                 # build, migrate, assert, tear down
#   docker compose -f docker-compose.acceptance.yml -f docker-compose.unbound.yml up -d
#
# The Unbound sidecar comes from docker-compose.unbound.yml: ADR-0075 made
# the validating resolver mandatory, so the acceptance stack runs the same
# resolver the production stack runs. The stack is started in phases by
# scripts/acceptance/cloud-acceptance.mjs, because the migrations have to
# be applied by the OWNER role before the app role connects — exactly the
# split the production deployment has.

name: dominus-acceptance

services:
  postgres:
    image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
    environment:
      POSTGRES_USER: dominus
      # The passwords come from the environment, and the runner sets them:
      # a tracked file must not carry a credential-shaped URI (CLAUDE.md).
      # These are local, throwaway values: they never leave the machine
      # that runs the suite, and the whole stack is destroyed with it.
      POSTGRES_PASSWORD: ${ACCEPTANCE_POSTGRES_PASSWORD:?ACCEPTANCE_POSTGRES_PASSWORD must be set (scripts/acceptance/cloud-acceptance.mjs sets it)}
      POSTGRES_DB: dominus
      # The non-superuser application role (ADR-0038/0047). The app
      # connects as dominus_app, never as the owner: a superuser bypasses
      # row-level security even with FORCE ROW LEVEL SECURITY, so tenant
      # isolation would rest on application filters alone.
      DOMINUS_APP_PASSWORD: ${ACCEPTANCE_APP_PASSWORD:?ACCEPTANCE_APP_PASSWORD must be set (scripts/acceptance/cloud-acceptance.mjs sets it)}
    volumes:
      - pgdata:/var/lib/postgresql/data
      # The same init script the production database uses: it creates the
      # app role and the default privileges that let the migrations run.
      - ./deploy/postgres/init-app-role.sh:/docker-entrypoint-initdb.d/init-app-role.sh:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dominus -d dominus"]
      interval: 5s
      timeout: 5s
      retries: 20
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  redis:
    image: redis:7-alpine@sha256:e7723ff73d963f5cc6d9c4643ea3d989527a402a319239054e9472a7fb9219a2
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 20
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  api:
    build:
      context: .
      dockerfile: Dockerfile
      target: api
    image: dominus-api:acceptance
    ports:
      - "127.0.0.1:${ACCEPTANCE_PORT:-3210}:3000"
    environment:
      - NODE_ENV=production
      - HOST=0.0.0.0
      - PORT=3000
      # The app role, not the owner: this is what makes the RLS proof
      # meaningful. The URL comes from the environment (see above): a
      # tracked file must not carry a credential-shaped URI.
      - DATABASE_URL=${ACCEPTANCE_APP_DATABASE_URL:?ACCEPTANCE_APP_DATABASE_URL must be set (scripts/acceptance/cloud-acceptance.mjs sets it)}
      - REDIS_URL=redis://redis:6379
      - AUTH_PROVIDER=db
      - AUTO_PROVISION_TENANTS=true
      - USAGE_ENFORCEMENT_ENABLED=true
      - METRICS_TOKEN=acceptance-metrics-token
      # Stripe is configured with test-shaped values: the webhook route is
      # only mounted when billing is configured, and the suite signs its
      # own events, so no network call is ever made. The values are not
      # credential-shaped: they are placeholders, not keys.
      - STRIPE_SECRET_KEY=acceptance-stripe-key
      - STRIPE_PUBLISHABLE_KEY=acceptance-stripe-publishable-key
      - STRIPE_WEBHOOK_SECRET=acceptance-webhook-secret
      - STRIPE_PRICE_ID_PRO_MONTHLY=price_acceptance_pro_monthly
      - STRIPE_PRICE_ID_PRO_YEARLY=price_acceptance_pro_yearly
      - STRIPE_PRICE_ID_TEAM_MONTHLY=price_acceptance_team_monthly
      - STRIPE_PRICE_ID_TEAM_YEARLY=price_acceptance_team_yearly
      - STRIPE_PRICE_ID_ENTERPRISE_MONTHLY=price_acceptance_enterprise_monthly
      - STRIPE_PRICE_ID_ENTERPRISE_YEARLY=price_acceptance_enterprise_yearly
      # A test identity, so the legal surface is complete and the boot log
      # stays readable. It is NOT an operator identity: the launch gate
      # (npm run launch:check) is what decides whether a real deployment
      # may publish a legal notice.
      - LEGAL_ENTITY_NAME=Dominus Acceptance Suite
      - LEGAL_ENTITY_KIND=company
      - LEGAL_REGISTERED_ADDRESS=Acceptance Lab, Via Test 1, 00100 Roma
      - LEGAL_VAT_ID=IT00000000000
      - LEGAL_REA_NUMBER=PR-000000
      - LEGAL_PEC=acceptance@example.invalid
      - LEGAL_CONTACT_EMAIL=acceptance@example.invalid
      - LEGAL_PRIVACY_EMAIL=privacy@example.invalid
      - RATE_LIMIT_MAX=10000
      # The launch gate probes the public surface repeatedly from one IP, and
      # the acceptance suite runs it after its own requests. The production
      # limits stay untouched; the throwaway stack raises them so a 429 is
      # never mistaken for a defect (and the input-validation check sees the
      # 400 it is testing).
      - PUBLIC_RATE_LIMIT_MAX=10000
      - PER_DOMAIN_RATE_LIMIT_MAX=10000
      - POST_RATE_LIMIT_MAX=10000
      - TRUST_PROXY_DEPTH=0
      - LOG_LEVEL=info
      - WORKER_ENABLED=false
      - SCHEDULER_ENABLED=false
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "node", "dist/app/healthcheck-cli.js"]
      interval: 5s
      timeout: 5s
      retries: 30
      start_period: 10s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  worker:
    build:
      context: .
      dockerfile: Dockerfile
      target: worker
    image: dominus-worker:acceptance
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${ACCEPTANCE_APP_DATABASE_URL:?ACCEPTANCE_APP_DATABASE_URL must be set (scripts/acceptance/cloud-acceptance.mjs sets it)}
      - REDIS_URL=redis://redis:6379
      - AUTH_PROVIDER=db
      - AUTO_PROVISION_TENANTS=true
      - USAGE_ENFORCEMENT_ENABLED=true
      - WORKER_ENABLED=true
      - SCHEDULER_ENABLED=false
      - WORKER_CONCURRENCY=2
      - JOB_QUEUE_POLL_INTERVAL_MS=500
      - BACKUP_DIR=/backups
      - RATE_LIMIT_MAX=10000
      - LOG_LEVEL=info
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - backups:/backups
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  scheduler:
    build:
      context: .
      dockerfile: Dockerfile
      target: scheduler
    image: dominus-scheduler:acceptance
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${ACCEPTANCE_APP_DATABASE_URL:?ACCEPTANCE_APP_DATABASE_URL must be set (scripts/acceptance/cloud-acceptance.mjs sets it)}
      - REDIS_URL=redis://redis:6379
      - AUTH_PROVIDER=db
      - AUTO_PROVISION_TENANTS=true
      - USAGE_ENFORCEMENT_ENABLED=true
      - WORKER_ENABLED=false
      - SCHEDULER_ENABLED=true
      - BACKUP_DIR=/backups
      - RATE_LIMIT_MAX=10000
      - LOG_LEVEL=info
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - backups:/backups
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  pgdata:
  backups: