docker-compose.acceptance.yml
Rama master · réplica de lectura del repositorio original.
# 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: