cards/infrastructure/docker-compose.production.yml
Till JS e1ddbf34b3
Some checks are pending
CI / validate (push) Waiting to run
security(cards): fail-secure dev-stub, headers, rate-limit, dsgvo audit
Behebt live verifiziertes Auth-Bypass auf cardecky-api.mana.how
(X-User-Id → founder-Tier) und zieht im selben Patch das fehlende
Operations-/Compliance-Fundament nach.

* Auth-Middleware fail-secure: opt-in via CARDS_AUTH_DEV_STUB="true"
  (war opt-out, Default true). Compose-Default flipped auf "false",
  NODE_ENV="production" für cards-api ergänzt, env-Template
  dokumentiert. vitest.config.ts + tests/setup.ts aktivieren den
  Stub gezielt für Test-Suiten.
* Security-Headers: Hono secureHeaders() in apps/api,
  SvelteKit hooks.server.ts mit X-Frame/X-Content-Type/Referrer/
  HSTS in apps/web. CSP bewusst ausgelassen — eigener Sprint.
* CORS-localhost-Whitelist nur außerhalb Prod.
* Rate-Limiting (in-memory sliding window, dependency-frei) auf
  share.receive 60/min/IP, media.upload 30/min/user,
  decks.generate + decks.from-image 10/min/user, dsgvo.* 10/min/IP.
* Health-Endpoint mit echter DB- und MinIO-Probe; /healthz bleibt
  Liveness, /healthz/details ist Readiness mit 503 bei Failure.
* DSGVO-Honesty: storage_ok + storage_error im Response (statt
  schluckend console.warn), Account-UI zeigt Fehler-Toast.
* Audit-Log: strukturierte JSON-Zeile (kind: "audit") auf stdout
  für /dsgvo/export, /dsgvo/delete, /me/export, /me/delete.
* Bug-Fix: duplizierte case "multiple-choice"-Clause in fsrs.ts.

Verifiziert: apps/api 17 Files / 104 Tests grün, apps/web check
0 errors. Deploy auf Mac Mini steht noch aus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:56:03 +02:00

123 lines
4.6 KiB
YAML

# Production-Stack für Cards auf dem Mac Mini.
#
# Lebt unter ~/projects/cards/ auf mana-server (Forgejo-Klon von
# git.mana.how/till/cards). Build-Contexte zeigen relativ in den
# Repo, kein externes Image-Registry — Cards ist Greenfield-eigenständig
# (Strategie B), kein Plattform-Coupling.
#
# Ports auf dem Mac Mini:
# cards-postgres: 5436 (Plattform 5432, Dev 5435 sind belegt)
# cards-minio S3: 9210 (cadvisor hat 9110 belegt)
# cards-minio UI: 9211
# cards-api: 3191 (alt war 3072 → cards-api.mana.how)
# cards-web: 5181 (alt war 5180 → cards.mana.how)
#
# Cutover (2026-05-08): cardecky.mana.how + cardecky-api.mana.how
# zeigen via Cloudflare-Tunnel auf diese Container. Alte Hostnames
# cards.mana.how / cards-api.mana.how → nginx :4400 → 301 zu
# cardecky.* (User-Bookmark-Erhalt).
#
# Start (von ~/projects/cards/ auf mana-server):
# docker compose -f infrastructure/docker-compose.production.yml \
# --env-file infrastructure/.env.production up -d --build
#
# Stop:
# docker compose -f infrastructure/docker-compose.production.yml down
services:
cards-postgres:
image: postgres:16-alpine
container_name: cards-postgres
restart: unless-stopped
environment:
POSTGRES_USER: cards
POSTGRES_PASSWORD: ${CARDS_DB_PASSWORD:?missing CARDS_DB_PASSWORD}
POSTGRES_DB: cards
ports:
- '127.0.0.1:5436:5432'
volumes:
- /Volumes/ManaData/cards/postgres:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U cards -d cards']
interval: 5s
timeout: 3s
retries: 20
cards-minio:
image: minio/minio:latest
container_name: cards-minio
restart: unless-stopped
command: server /data --console-address ':9001'
environment:
MINIO_ROOT_USER: cardsadmin
MINIO_ROOT_PASSWORD: ${CARDS_S3_SECRET_KEY:?missing CARDS_S3_SECRET_KEY}
ports:
- '127.0.0.1:9210:9000'
- '127.0.0.1:9211:9001'
volumes:
- /Volumes/ManaData/cards/minio:/data
healthcheck:
test: ['CMD', 'mc', 'ready', 'local']
interval: 5s
timeout: 3s
retries: 10
cards-api:
image: cards-api:local
container_name: cards-api
build:
context: ../
dockerfile: apps/api/Dockerfile
args:
NPM_AUTH_TOKEN: ${NPM_AUTH_TOKEN:?missing NPM_AUTH_TOKEN}
restart: unless-stopped
depends_on:
cards-postgres:
condition: service_healthy
cards-minio:
condition: service_healthy
environment:
DATABASE_URL: 'postgresql://cards:${CARDS_DB_PASSWORD}@cards-postgres:5432/cards'
CARDS_API_PORT: 3081
CARDS_API_VERSION: ${CARDS_API_VERSION:-1.0.0}
CARDS_PUBLIC_URL: https://cardecky.mana.how
CARDS_DSGVO_SERVICE_KEY: ${CARDS_DSGVO_SERVICE_KEY:?missing CARDS_DSGVO_SERVICE_KEY}
CARDS_S3_ENDPOINT: cards-minio
CARDS_S3_PORT: 9000
CARDS_S3_USE_SSL: 'false'
CARDS_S3_ACCESS_KEY: cardsadmin
CARDS_S3_SECRET_KEY: ${CARDS_S3_SECRET_KEY}
CARDS_S3_BUCKET: cards-media
MANA_AUTH_URL: https://auth.mana.how
MANA_CREDITS_URL: https://credits.mana.how
CARDS_MANA_SERVICE_KEY: ${CARDS_MANA_SERVICE_KEY:-}
# Fail-secure: opt-in. Auf der Prod-Box gar nicht setzen
# ⇒ Bypass AUS. Nur für gezielte lokale Diagnose temporär
# auf 'true' setzen (und sofort wieder rausnehmen).
CARDS_AUTH_DEV_STUB: ${CARDS_AUTH_DEV_STUB:-false}
NODE_ENV: production
ports:
- '127.0.0.1:3191:3081'
cards-web:
image: cards-web:local
container_name: cards-web
build:
context: ../
dockerfile: apps/web/Dockerfile
args:
NPM_AUTH_TOKEN: ${NPM_AUTH_TOKEN:?missing NPM_AUTH_TOKEN}
restart: unless-stopped
depends_on:
- cards-api
environment:
# SvelteKit `$env/dynamic/public` liest zur Runtime — daher
# hier statt als Build-Arg. Wert landet im SSR-Init-Snapshot
# und in client-fetches.
PUBLIC_CARDS_API_URL: https://cardecky-api.mana.how
PUBLIC_MANA_AUTH_URL: https://auth.mana.how
PUBLIC_AUTH_WEB_URL: https://auth.mana.how
CARDS_API_URL: https://cardecky-api.mana.how
NODE_ENV: production
ports:
- '127.0.0.1:5181:3000'