Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,460 @@
|
||||
# Coolify-Deployment (PREBUILT-Variante) — zieht fertige Images aus der Registry
|
||||
# statt auf dem Host zu bauen (Plan B: langsamer/timeoutender Host-Build umgehen).
|
||||
# Images vorher bauen+pushen: scripts/build-and-push-images.sh
|
||||
# Registry/Tag via Coolify-Env: REGISTRY (Default git.certvia.de/msolarczek), IMAGE_TAG (Default main).
|
||||
# Abgeleitet von docker-compose.coolify.yml.
|
||||
# Unterschiede zum lokalen Dev-Compose:
|
||||
# - keine host "ports": Coolify-Proxy routet die Domain intern auf app:3000
|
||||
# - Konfiguration über Coolify-Env-Variablen statt env_file: .env
|
||||
# - Service "migrate": Init-Job (prisma migrate deploy + Rollen-Rechte-Sync), läuft einmalig VOR app
|
||||
# - kein mailhog (Dev); Service "worker" = SEC1 Mail-Worker (BullMQ/Redis-Queue)
|
||||
# In Coolify als "Docker Compose Location" -> docker-compose.coolify.yml setzen.
|
||||
#
|
||||
# Härtung (F-11/F-18):
|
||||
# - F-11: migrate nutzt die schlanke "migrate"-Stage (kein Next-Build), Images gepinnt.
|
||||
# - F-18: Backend-Dienste (postgres/redis/garage/migrate) im internen Netz ohne Egress;
|
||||
# nur app zusätzlich im default-Netz (Coolify-Proxy). Redis mit Passwort.
|
||||
# Überall no-new-privileges, cap_drop ALL (+gezielte cap_add), Ressourcenlimits.
|
||||
services:
|
||||
# Einmaliger Migrations-Job. Nutzt die schlanke "migrate"-Stage (Prisma-CLI/tsx + src,
|
||||
# aber OHNE next build/.next) — kleinere Angriffsfläche als die frühere builder-Stage.
|
||||
# Nach `migrate deploy` läuft der Rollen-Rechte-Sync (scripts/sync-role-permissions.ts):
|
||||
# additiv + idempotent, zieht neu eingeführte Rechte für bestehende Mandanten nach
|
||||
# (F-06 × F-10). Bei 0 Änderungen ein No-op. Betroffene Nutzer danach neu einloggen.
|
||||
# Optionaler Demo-Seed nur, wenn RUN_DEMO_SEED=true (Testserver). Idempotent (upserts).
|
||||
# In Produktion die Variable NICHT setzen; stattdessen BOOTSTRAP_ADMIN.
|
||||
migrate:
|
||||
image: ${REGISTRY:-git.certvia.de/msolarczek}/certvia-migrate:${IMAGE_TAG:-main}
|
||||
command: sh -c "npx prisma migrate deploy && echo '>> Rollen-Rechte-Sync läuft…' && npx tsx scripts/sync-role-permissions.ts && echo '>> Vorlagen-Sync läuft…' && npx tsx scripts/sync-policy-templates.ts && if [ \"$$RUN_DEMO_SEED\" = \"true\" ]; then echo '>> Demo-Seed läuft…'; npx tsx prisma/seed.ts; fi && if [ \"$$BOOTSTRAP_ADMIN\" = \"true\" ]; then echo '>> Bootstrap-Admin läuft…'; npx tsx scripts/bootstrap-admin.ts; fi"
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
# Härtung §1: Passwort-Pepper (Argon2 `secret`). Seed/Bootstrap hashen Passwörter
|
||||
# → fail-secure, ohne gültigen 32-Byte-Hex wirft hashPassword und der Job scheitert.
|
||||
# Muss identisch zum app-Service sein (sonst verifizieren die Hashes später nicht).
|
||||
PASSWORD_PEPPER: ${PASSWORD_PEPPER}
|
||||
# Test: Demo-Seed. Produktiv: stattdessen BOOTSTRAP_ADMIN (Erst-Superadmin).
|
||||
RUN_DEMO_SEED: ${RUN_DEMO_SEED:-false}
|
||||
BOOTSTRAP_ADMIN: ${BOOTSTRAP_ADMIN:-false}
|
||||
BOOTSTRAP_ADMIN_EMAIL: ${BOOTSTRAP_ADMIN_EMAIL:-}
|
||||
BOOTSTRAP_ADMIN_PASSWORD: ${BOOTSTRAP_ADMIN_PASSWORD:-}
|
||||
BOOTSTRAP_ADMIN_NAME: ${BOOTSTRAP_ADMIN_NAME:-}
|
||||
BOOTSTRAP_TENANT_NAME: ${BOOTSTRAP_TENANT_NAME:-}
|
||||
BOOTSTRAP_TENANT_SLUG: ${BOOTSTRAP_TENANT_SLUG:-}
|
||||
BOOTSTRAP_TENANT_SHORT: ${BOOTSTRAP_TENANT_SHORT:-}
|
||||
BOOTSTRAP_TENANT_SECTOR: ${BOOTSTRAP_TENANT_SECTOR:-}
|
||||
networks:
|
||||
- backend
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 1G
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: "no"
|
||||
|
||||
app:
|
||||
image: ${REGISTRY:-git.certvia.de/msolarczek}/certvia-app:${IMAGE_TAG:-main}
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
# F-04: Row Level Security scharf. Der migrate-Job oben läuft bewusst
|
||||
# weiter mit der Owner-DATABASE_URL (BYPASSRLS). Nur der App-Prozess
|
||||
# verbindet als eingeschränkte Rolle isms_app (NOBYPASSRLS) über
|
||||
# RLS_DATABASE_URL und setzt app.tenant_id pro Transaktion.
|
||||
RLS_ENFORCED: ${RLS_ENFORCED:-false}
|
||||
RLS_DATABASE_URL: ${RLS_DATABASE_URL}
|
||||
# F-18: REDIS_URL wird AUS REDIS_PASSWORD abgeleitet (nicht separat setzen!) — so
|
||||
# bleibt sie immer konsistent zum `--requirepass` des redis-Dienstes und Coolify
|
||||
# kann sie nicht als „managed" sperren. In Coolify NUR REDIS_PASSWORD setzen.
|
||||
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
||||
AUTH_SECRET: ${AUTH_SECRET}
|
||||
# Härtung §1: Passwort-Pepper (Argon2 `secret`). Fail-secure — ohne gültigen
|
||||
# 32-Byte-Hex wirft jeder Login/hash. Muss IDENTISCH zum migrate-Job sein und
|
||||
# ⚠ nach dem Setzen nicht mehr ändern (sonst verifizieren bestehende Hashes nicht).
|
||||
PASSWORD_PEPPER: ${PASSWORD_PEPPER}
|
||||
# SEC3-d: TOTP-Secrets werden „at rest" mit AES-256-GCM verschlüsselt
|
||||
# (src/server/secret-crypto.ts). Der Schlüssel leitet sich aus MFA_ENC_KEY
|
||||
# ab, sonst aus AUTH_SECRET — daher OPTIONAL (leer = Fallback AUTH_SECRET).
|
||||
# ⚠ Nach dem Setzen nicht mehr ändern: macht bereits verschlüsselte
|
||||
# TOTP-Secrets unlesbar (kein Umschlüssel-Tool).
|
||||
MFA_ENC_KEY: ${MFA_ENC_KEY:-}
|
||||
AUTH_URL: ${AUTH_URL}
|
||||
# Hinter dem Coolify/Traefik-Proxy zwingend für Auth.js v5 (sonst UntrustedHost).
|
||||
AUTH_TRUST_HOST: ${AUTH_TRUST_HOST:-true}
|
||||
S3_ENDPOINT: ${S3_ENDPOINT}
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
||||
S3_BUCKET: ${S3_BUCKET}
|
||||
# S1: echter S3-Adapter (src/server/storage/adapter.ts). Region ist für MinIO
|
||||
# beliebig (Default us-east-1); überschreibbar für echtes AWS-S3.
|
||||
S3_REGION: ${S3_REGION:-us-east-1}
|
||||
# Lokaler (persistenter) Backup-Zielspeicher, wenn der Betreiber im Portal „Lokal"
|
||||
# wählt bzw. als Env-Fallback. Muss auf das gemountete `backups`-Volume zeigen,
|
||||
# sonst sind Sicherungen beim Redeploy flüchtig. DB-Config hat Vorrang vor dieser Var.
|
||||
BACKUP_LOCAL_DIR: ${BACKUP_LOCAL_DIR:-/app/.backups}
|
||||
AI_PROVIDER: ${AI_PROVIDER}
|
||||
AI_API_KEY: ${AI_API_KEY}
|
||||
SMTP_HOST: ${SMTP_HOST}
|
||||
SMTP_PORT: ${SMTP_PORT}
|
||||
SMTP_USER: ${SMTP_USER}
|
||||
SMTP_PASSWORD: ${SMTP_PASSWORD}
|
||||
SMTP_FROM: ${SMTP_FROM}
|
||||
# Persistenter lokaler Backup-Zielspeicher (überlebt Redeploys).
|
||||
volumes:
|
||||
- backups:/app/.backups
|
||||
# app im internen Netz (Backend-Zugriff) UND im default-Netz (Coolify-Proxy-Routing).
|
||||
networks:
|
||||
- backend
|
||||
- default
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 1G
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
garage:
|
||||
condition: service_healthy
|
||||
garage-provision:
|
||||
condition: service_completed_successfully
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
healthcheck:
|
||||
# Dependency-frei (node ist im Image vorhanden); alles < 500 gilt als gesund
|
||||
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
restart: unless-stopped
|
||||
|
||||
# SEC1: Mail-Worker. Eigener Langläufer-Prozess, der die BullMQ/Redis-Queue
|
||||
# abarbeitet (Zustellung mit Retry/DLQ + täglicher Fristen-/Reminder-Job).
|
||||
# Ist REDIS_URL gesetzt, reiht die App Jobs ein und DIESER Worker stellt zu;
|
||||
# ohne REDIS_URL versendet die App inline und der Worker wird nicht gebraucht.
|
||||
# Nutzt das schlanke "migrate"-Image (tsx + src + Mail-Deps + Prisma-Client).
|
||||
# Owner-DATABASE_URL (kein RLS): der Worker arbeitet mandantenübergreifend.
|
||||
# Im default-Netz (Egress), damit der externe SMTP-Server erreichbar ist.
|
||||
worker:
|
||||
image: ${REGISTRY:-git.certvia.de/msolarczek}/certvia-migrate:${IMAGE_TAG:-main}
|
||||
command: ["npx", "tsx", "scripts/mail-worker.ts"]
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
||||
# SMTP: fehlt die Konfiguration, bleibt der Worker aktiv und meldet den
|
||||
# Zustand (Jobs bleiben pending) — kein Neustart-Rennen beim Nachreichen.
|
||||
SMTP_HOST: ${SMTP_HOST}
|
||||
SMTP_PORT: ${SMTP_PORT}
|
||||
SMTP_SECURE: ${SMTP_SECURE:-}
|
||||
SMTP_USER: ${SMTP_USER}
|
||||
SMTP_PASSWORD: ${SMTP_PASSWORD}
|
||||
SMTP_FROM: ${SMTP_FROM}
|
||||
# Absolute Links in Mails (Reset etc.): APP_BASE_URL, sonst AUTH_URL.
|
||||
AUTH_URL: ${AUTH_URL}
|
||||
networks:
|
||||
- backend
|
||||
- default
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 512M
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
|
||||
# Backup-Worker: arbeitet die BullMQ-Queue "backup-ops" ab (Portal-Restore,
|
||||
# "Export jetzt", DSGVO-Zustellung). OHNE diesen Dienst bleiben angestoßene Jobs
|
||||
# dauerhaft auf "queued" (die App reiht nur ein, führt NICHT inline aus).
|
||||
# Owner-DATABASE_URL (kein RLS): Restore/Export laufen mandantenübergreifend und
|
||||
# brauchen einen Superuser-Owner (session_replication_role beim Restore).
|
||||
# Braucht das S3/MinIO-Backup-Bucket (S3_*) und den Backup-Schlüssel (BACKUP_ENC_KEY,
|
||||
# Fallback AUTH_SECRET). concurrency ist im Worker seriell (destruktiv).
|
||||
backup-worker:
|
||||
image: ${REGISTRY:-git.certvia.de/msolarczek}/certvia-migrate:${IMAGE_TAG:-main}
|
||||
command: ["npx", "tsx", "scripts/backup-worker.ts"]
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
||||
AUTH_SECRET: ${AUTH_SECRET}
|
||||
# Pepper wird von der Fail-Secure-Startprüfung erwartet (assertSecureEnv).
|
||||
PASSWORD_PEPPER: ${PASSWORD_PEPPER}
|
||||
# Backup-Artefakt-Verschlüsselung (AES-256-GCM); ohne eigenen Key = AUTH_SECRET.
|
||||
BACKUP_ENC_KEY: ${BACKUP_ENC_KEY:-}
|
||||
# Objektspeicher für Backup-Artefakte + DSGVO-Pakete (S3/MinIO). Fehlt es,
|
||||
# nutzt der Store einen lokalen (ephemeren!) Ordner — für Prod S3_* setzen.
|
||||
S3_ENDPOINT: ${S3_ENDPOINT:-}
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY:-}
|
||||
S3_BUCKET: ${S3_BUCKET:-}
|
||||
S3_REGION: ${S3_REGION:-}
|
||||
# Lokaler (persistenter) Backup-Zielspeicher — identischer Mount wie beim app-Service,
|
||||
# damit „Lokal" für Worker (schreibt Artefakte) und app (liest im Portal) dasselbe
|
||||
# Volume sieht. DB-Config aus dem Betreiber-Portal hat Vorrang vor dieser Var.
|
||||
BACKUP_LOCAL_DIR: ${BACKUP_LOCAL_DIR:-/app/.backups}
|
||||
AUTH_URL: ${AUTH_URL}
|
||||
# Gleiches persistentes Volume wie app (gemeinsamer lokaler Zielspeicher).
|
||||
volumes:
|
||||
- backups:/app/.backups
|
||||
networks:
|
||||
- backend
|
||||
- default
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 512M
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
|
||||
# IM-D — Inbound-Mail-Worker (E-Mail-to-Ticket). Holt Mails vom Catch-all-Postfach
|
||||
# (vorfall-<token>@in.certvia.de) per IMAP ab und legt daraus Vorfälle an bzw. reiht
|
||||
# unklare Mails in die Betreiber-Review. Kein Redis nötig (IMAP-Poller, keine Queue).
|
||||
# Ohne INCIDENT_IMAP_* beendet sich der Prozess sauber („nicht konfiguriert").
|
||||
incident-inbound-worker:
|
||||
image: ${REGISTRY:-git.certvia.de/msolarczek}/certvia-migrate:${IMAGE_TAG:-main}
|
||||
command: ["npx", "tsx", "scripts/incident-inbound-worker.ts"]
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
AUTH_SECRET: ${AUTH_SECRET}
|
||||
# Pepper wird von der Fail-Secure-Startprüfung erwartet (assertSecureEnv).
|
||||
PASSWORD_PEPPER: ${PASSWORD_PEPPER}
|
||||
# IMAP-Zugang des Catch-all-Postfachs. Fehlt es, beendet sich der Worker sauber.
|
||||
INCIDENT_IMAP_HOST: ${INCIDENT_IMAP_HOST:-}
|
||||
INCIDENT_IMAP_PORT: ${INCIDENT_IMAP_PORT:-993}
|
||||
INCIDENT_IMAP_USER: ${INCIDENT_IMAP_USER:-}
|
||||
INCIDENT_IMAP_PASSWORD: ${INCIDENT_IMAP_PASSWORD:-}
|
||||
INCIDENT_IMAP_TLS: ${INCIDENT_IMAP_TLS:-true}
|
||||
INCIDENT_IMAP_MAILBOX: ${INCIDENT_IMAP_MAILBOX:-INBOX}
|
||||
INCIDENT_IMAP_POLL_MS: ${INCIDENT_IMAP_POLL_MS:-60000}
|
||||
# Muss zur Catch-all-Subdomain passen (Ableitung der Intake-Adressen).
|
||||
INCIDENT_INTAKE_DOMAIN: ${INCIDENT_INTAKE_DOMAIN:-in.certvia.de}
|
||||
AUTH_URL: ${AUTH_URL}
|
||||
networks:
|
||||
- backend
|
||||
- default
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 512M
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: pgvector/pgvector:0.8.0-pg16
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
- backend
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
# Postgres-Entrypoint chownt das Datadir und wechselt per gosu auf den postgres-User.
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 1G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7.4.2-alpine
|
||||
# F-18: Redis mit Passwort. REDIS_URL (app) muss dieses Passwort tragen.
|
||||
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
networks:
|
||||
- backend
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
# redis-alpine-Entrypoint chownt /data und wechselt per gosu auf den redis-User.
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 256M
|
||||
restart: unless-stopped
|
||||
|
||||
# Objektspeicher: Garage (S3-kompatibel) — ersetzt den früheren minio-Service
|
||||
# (MinIO Community EOL/Maintenance-Mode). Konzept: docs/KONZEPT-garage-migration.md.
|
||||
# Buckets/Keys werden NICHT über die S3-API angelegt, sondern vom Init-Job
|
||||
# "garage-provision" (Admin-API). Nichts nach außen (kein Traefik/ports:) —
|
||||
# rein clusterintern, wie minio zuvor. Version gepinnt (kein latest).
|
||||
garage:
|
||||
# Image mit eingebackener deploy/garage.toml (Dockerfile-Stage „garage"). KEIN
|
||||
# Bind-Mount der Config: Coolify legt relative Bind-Quellen sonst als Verzeichnis an
|
||||
# → Garage bekäme /etc/garage.toml als Ordner („IO error: Is a directory").
|
||||
image: ${REGISTRY:-git.certvia.de/msolarczek}/certvia-garage:${IMAGE_TAG:-main}
|
||||
# Secrets stehen NICHT in der eingebackenen deploy/garage.toml — der Daemon liest
|
||||
# rpc_secret/admin_token aus der Env (in Coolify literal setzen, nicht via ${…}).
|
||||
environment:
|
||||
GARAGE_RPC_SECRET: ${GARAGE_RPC_SECRET}
|
||||
GARAGE_ADMIN_TOKEN: ${GARAGE_ADMIN_TOKEN}
|
||||
volumes:
|
||||
# KRITISCH: meta enthält Bucket-/Key-/Layout-Definitionen → MUSS ins Host-Backup
|
||||
# (analog pgdata). Ohne meta sind die Objektdaten nicht adressierbar.
|
||||
- garage_meta:/var/lib/garage/meta
|
||||
- garage_data:/var/lib/garage/data
|
||||
networks:
|
||||
- backend
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
healthcheck:
|
||||
# Distroless-Image (nur /garage): der CLI-Status dient als Health-Signal.
|
||||
# Verbindet auf rpc_public_addr (loopback) → unabhängig von Compose-DNS.
|
||||
test: ["CMD", "/garage", "status"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 1G
|
||||
restart: unless-stopped
|
||||
|
||||
# Idempotenter Provisioning-Job (analog "migrate", restart:no): stellt aus einer
|
||||
# leeren Garage Layout + Bucket + Access-Key + Rechte her (Garage-Admin-API, HTTP).
|
||||
# Läuft bei JEDEM Deploy; "already exists" = Erfolg. Ohne GARAGE_ADMIN_TOKEN No-op.
|
||||
# Nutzt das schlanke "migrate"-Image (Node/tsx) — kein Garage-Binary nötig.
|
||||
garage-provision:
|
||||
image: ${REGISTRY:-git.certvia.de/msolarczek}/certvia-migrate:${IMAGE_TAG:-main}
|
||||
command: ["npx", "tsx", "scripts/garage-provision.ts"]
|
||||
environment:
|
||||
GARAGE_ADMIN_URL: http://garage:3903
|
||||
GARAGE_ADMIN_TOKEN: ${GARAGE_ADMIN_TOKEN}
|
||||
# Der zu importierende Garage-Key = App-Key (App-Env und Garage synchron).
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
||||
S3_BUCKET: ${S3_BUCKET:-isms-documents}
|
||||
# Optionaler separater Backup-Bucket (nur falls Backup-Store auf S3 statt lokal).
|
||||
BACKUP_S3_BUCKET: ${BACKUP_S3_BUCKET:-}
|
||||
# Single-Node-Layout (nominale Kapazität/Zone; für Prod ggf. anheben).
|
||||
GARAGE_ZONE: ${GARAGE_ZONE:-dc1}
|
||||
GARAGE_CAPACITY_BYTES: ${GARAGE_CAPACITY_BYTES:-100000000000}
|
||||
networks:
|
||||
- backend
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 256M
|
||||
depends_on:
|
||||
garage:
|
||||
condition: service_healthy
|
||||
restart: "no"
|
||||
|
||||
# ── ROLLBACK-NETZ (bis Garage abgenommen ist; danach entfernen) ───────────────
|
||||
# Der alte minio-Service bleibt vorübergehend auskommentiert stehen, damit ein
|
||||
# Rollback ohne Repo-Archäologie möglich ist. NACH erfolgreicher Garage-Abnahme
|
||||
# diesen Block UND das Volume "miniodata" (unten) entfernen.
|
||||
# minio:
|
||||
# image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
# command: server /data --console-address ":9001"
|
||||
# environment:
|
||||
# MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
# MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
# volumes:
|
||||
# - miniodata:/data
|
||||
# networks:
|
||||
# - backend
|
||||
# security_opt:
|
||||
# - "no-new-privileges:true"
|
||||
# cap_drop:
|
||||
# - ALL
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: "0.5"
|
||||
# memory: 512M
|
||||
# restart: unless-stopped
|
||||
|
||||
networks:
|
||||
# Internes Netz ohne Egress/Host-Exposure für die Backend-Dienste.
|
||||
backend:
|
||||
internal: true
|
||||
# default: von Coolify für das Proxy-Routing zur app genutzt (mit Egress).
|
||||
default:
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
redisdata:
|
||||
# Garage-Objektspeicher. garage_meta ist KRITISCH (Bucket-/Key-/Layout-Definitionen)
|
||||
# und MUSS in die Host-Backup-Strategie (analog pgdata) — ohne meta sind die Daten
|
||||
# in garage_data nicht adressierbar.
|
||||
garage_meta:
|
||||
garage_data:
|
||||
# ROLLBACK: altes MinIO-Datenvolume. Nach abgenommener Garage-Migration entfernen.
|
||||
miniodata:
|
||||
# Persistenter lokaler Backup-Zielspeicher (Lane „Konfigurierbarer Backup-Zielspeicher").
|
||||
# Von app + backup-worker unter /app/.backups gemountet, damit „Lokal" Redeploys überlebt.
|
||||
backups:
|
||||
Reference in New Issue
Block a user