Files
dtf-system/compose.local.yaml
Cauê Faleiros fc6f708e95 chore: restore a working localhost stack
docker-compose.yml became the production/R2 stack, but LOCAL_SETUP.md still
documented "docker compose up --build" against .env.example, which fails on
missing R2_ENDPOINT, SITE_DOMAIN and KANBAN_DOMAIN, and MinIO was gone.

Add compose.local.yaml: builds from source, MinIO storage, fake providers,
disposable credentials, an app database password distinct from the
administrator one, and published origins in ALLOWED_ORIGINS so browser writes
are not rejected. Correct the documented command and the Kanban login, which
listed a username the email-validated model rejects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 18:33:27 -03:00

198 lines
6.6 KiB
YAML

# Localhost development stack. Builds from source, uses MinIO, fake providers and
# disposable credentials. `docker-compose.yml` is the production/R2 stack and is
# NOT usable locally; the two are deliberately separate files.
#
# docker compose -f compose.local.yaml up --build
#
# Defaults here mirror `.env.example`; copy it to `.env` only to customise.
x-app: &app
build:
context: .
dockerfile: local/Dockerfile
environment: &environment
APP_ENV: local
DATABASE_URL: postgresql://${APP_DB_USER:-dtf_app}:${APP_DB_PASSWORD:-local-app-database-only}@db:5432/${POSTGRES_DB:-dtf_local}
S3_ENDPOINT: http://storage:9000
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:9000}
S3_BUCKET: ${S3_BUCKET:-dtf-local-artwork}
AWS_ACCESS_KEY_ID: ${S3_APP_USER:-dtf_app}
AWS_SECRET_ACCESS_KEY: ${S3_APP_PASSWORD:-local-app-storage-only}
AWS_DEFAULT_REGION: us-east-1
OPERATOR_EMAIL: ${OPERATOR_EMAIL:-operator@example.test}
OPERATOR_PASSWORD: ${OPERATOR_PASSWORD:-local-operator-only}
# The browser reaches the API through the Site gateway, so the published
# Site/Kanban origins must be accepted or every write is rejected 403.
PUBLIC_ORIGIN: http://localhost:${SITE_PORT:-8080}
ALLOWED_HOSTS: localhost,127.0.0.1
ALLOWED_ORIGINS: http://localhost:${SITE_PORT:-8080},http://localhost:${KANBAN_PORT:-8081},http://127.0.0.1:${SITE_PORT:-8080},http://127.0.0.1:${KANBAN_PORT:-8081}
COOKIE_SECURE: "false"
PAYMENT_ADAPTER: fake
FREIGHT_ADAPTER: fake
TINY_ADAPTER: fake
WHATSAPP_ADAPTER: fake
STORAGE_ADAPTER: s3-local
MOCK_FREIGHT_CENTS: ${MOCK_FREIGHT_CENTS:-1500}
MAX_UPLOAD_BYTES: ${MAX_UPLOAD_BYTES:-5368709120}
UPLOAD_PART_BYTES: ${UPLOAD_PART_BYTES:-8388608}
STORAGE_QUOTA_BYTES: ${STORAGE_QUOTA_BYTES:-53687091200}
OWNER_UPLOAD_QUOTA_BYTES: ${OWNER_UPLOAD_QUOTA_BYTES:-10737418240}
MAX_PENDING_UPLOADS: ${MAX_PENDING_UPLOADS:-10}
SCAN_MAX_BYTES: ${SCAN_MAX_BYTES:-134217728}
networks: [local]
init: true
security_opt: [no-new-privileges:true]
cap_drop: [ALL]
read_only: true
tmpfs: [/tmp]
logging:
driver: json-file
options: {max-size: "10m", max-file: "3"}
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-dtf_local}
POSTGRES_USER: ${POSTGRES_USER:-dtf_local}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-local-database-only}
volumes: [postgres-data:/var/lib/postgresql/data]
networks: [local]
healthcheck:
test: [CMD-SHELL, 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"']
interval: 5s
timeout: 3s
retries: 30
storage:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
command: server /data --console-address :9001
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-dtf_local}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-local-storage-only}
ports: ["127.0.0.1:9000:9000", "127.0.0.1:9001:9001"]
volumes: [storage-data:/data]
networks: [local, edge]
healthcheck:
test: [CMD, mc, ready, local]
interval: 5s
timeout: 3s
retries: 30
db-init:
build:
context: .
dockerfile: local/Dockerfile
command: python -m local.bootstrap
environment:
# Local only: the app role keeps a password distinct from the administrator.
DATABASE_ADMIN_URL: postgresql://${POSTGRES_USER:-dtf_local}:${POSTGRES_PASSWORD:-local-database-only}@db:5432/${POSTGRES_DB:-dtf_local}
APP_DB_USER: ${APP_DB_USER:-dtf_app}
APP_DB_PASSWORD: ${APP_DB_PASSWORD:-local-app-database-only}
networks: [local]
depends_on:
db: {condition: service_healthy}
restart: on-failure
storage-init:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
entrypoint: [/bin/sh, /init.sh]
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-dtf_local}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-local-storage-only}
S3_APP_USER: ${S3_APP_USER:-dtf_app}
S3_APP_PASSWORD: ${S3_APP_PASSWORD:-local-app-storage-only}
S3_BUCKET: ${S3_BUCKET:-dtf-local-artwork}
volumes:
- ./local/storage-init.sh:/init.sh:ro
- ./local/storage-policy.json:/policy.json:ro
- ./local/storage-lifecycle.json:/lifecycle.json:ro
networks: [local]
depends_on:
storage: {condition: service_healthy}
restart: on-failure
scanner:
image: clamav/clamav@sha256:9cb27d7660bdf66e9878c832cb433dd8aa152cfbe16f3c2c0084c80b04ae22b4
entrypoint: [clamd, --foreground=true, --config-file=/etc/clamav/clamd.conf]
volumes: [./local/clamd.conf:/etc/clamav/clamd.conf:ro]
networks: [local]
security_opt: [no-new-privileges:true]
healthcheck:
test: [CMD, clamdscan, --config-file=/etc/clamav/clamd.conf, --ping, "3"]
start_period: 60s
interval: 10s
timeout: 5s
retries: 30
deploy:
resources:
limits: {memory: 3G}
api:
<<: *app
command: uvicorn local.app:app --host 0.0.0.0 --port 8000 --no-access-log
depends_on:
db-init: {condition: service_completed_successfully}
storage-init: {condition: service_completed_successfully}
healthcheck:
test: [CMD, python, -c, "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 5s
timeout: 3s
retries: 30
worker:
<<: *app
command: python -m local.worker
depends_on:
api: {condition: service_healthy}
scanner: {condition: service_healthy}
healthcheck:
test: [CMD, python, -c, "import urllib.request; urllib.request.urlopen('http://localhost:8002/health')"]
interval: 5s
timeout: 3s
retries: 12
site:
build:
context: .
dockerfile: local/Dockerfile.web
environment:
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:9000}
ports:
- "127.0.0.1:${SITE_PORT:-8080}:80"
- "127.0.0.1:${API_PORT:-8000}:81"
networks: [local, edge]
depends_on:
api: {condition: service_healthy}
healthcheck:
test: [CMD, wget, -q, -O, /dev/null, http://127.0.0.1/health]
interval: 5s
timeout: 3s
retries: 12
kanban:
build:
context: .
dockerfile: local/Dockerfile.web
environment:
WEB_INDEX: kanban.html
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:9000}
ports: ["127.0.0.1:${KANBAN_PORT:-8081}:80"]
networks: [local, edge]
depends_on:
api: {condition: service_healthy}
healthcheck:
test: [CMD, wget, -q, -O, /dev/null, http://127.0.0.1/health]
interval: 5s
timeout: 3s
retries: 12
volumes:
postgres-data:
storage-data:
networks:
local:
internal: true
edge: