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>
This commit is contained in:
197
compose.local.yaml
Normal file
197
compose.local.yaml
Normal file
@@ -0,0 +1,197 @@
|
||||
# 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:
|
||||
Reference in New Issue
Block a user