Docker Compose Setup
Eviworx ITSM besteht aus 12 Docker-Containern für Production. Diese Seite beschreibt jeden Service mit Netzwerk, Startabhängigkeiten (depends_on), Health-Checks, Volumes und Härtung.
🐳
Container-Übersicht
Reverse-Proxy-Layer:
- 1. traefik - Reverse Proxy (Traefik v3.7)
Frontend-Layer:
- 2. frontend - React App (NGINX)
Backend-Layer:
- 3. backend - Node.js API
Data-Layer:
- 4. db - PostgreSQL 17
- 5. redis - Redis 8 (Cache/Queue)
Worker-Layer:
- 6. email-worker - Email Processing
- 7. job-worker - CronJobs
- 8. workflow-engine - Workflows
- 9. notification-worker - External APIs
- 10. report-generator - Reports & CSV Export
Security-Layer:
- 11. clamav - Virus Scanner
- 12. av-worker - Scan Worker
Service-Details
1. traefik (Reverse Proxy)
Zweck: Reverse Proxy, TLS-Terminierung, Load Balancing Technologie: • Traefik v3.7.7 • Port 80 (HTTP → HTTPS Redirect) • Port 443 (HTTPS) Konfiguration: • Keine Environment-Variables nötig (Konfiguration via YAML-Dateien) • traefik.yml (statische Konfiguration, read-only, inkl. forwardedHeaders.trustedIPs) • dynamic.yml (dynamische Konfiguration, read-only) • SSL-Zertifikate (cert.pem + cert.key, read-only) Externer Reverse Proxy: • Bei Betrieb hinter externem Proxy: forwardedHeaders.trustedIPs in traefik.yml + TRUSTED_PROXIES in .env konfigurieren Security-Hardening: • security_opt: no-new-privileges:true Health-Check: • traefik healthcheck --ping --ping.entrypoint=ping • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 10s Depends-On: • backend (service_healthy) • frontend (service_healthy) Restart-Policy: • unless-stopped
2. frontend (React App)
Zweck: React-19-Single-Page-Application, ausgeliefert über NGINX Technologie: • React 19+ with TypeScript • Vite Build-System • NGINX (Production Webserver) • Kein externer Port (nur expose: 80, Zugriff via Traefik) Health-Check: • wget -qO- http://localhost:80/health • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 10s Security-Hardening: • read_only: true (Filesystem read-only, tmpfs für /var/cache/nginx, /var/run, /tmp) • security_opt: no-new-privileges:true • cap_drop: NET_RAW, SYS_ADMIN, MKNOD Volumes: • sourcemaps:/opt/sourcemaps:rw (legt beim Start die .map-Files des eigenen Releases ab und behält die fünf neuesten Releases; Backend liest sie read-only) Depends-On: • backend (service_healthy) Restart-Policy: • unless-stopped
3. backend (Node.js API)
Zweck: REST-API, Geschäftslogik, RBAC, interne API für die Worker Technologie: • Node.js 20+ with TypeScript • Express.js Framework • Prisma ORM (PostgreSQL) • Port 3000 Wichtige Environment-Variables: • DATABASE_URL: Full DB-Access (helpdesk_user) • JWT_SECRET: Für Token-Signierung (ÄNDERN!) • SHARE_SECRET: Signiert öffentliche Freigabe-Links — PFLICHT, Backend startet sonst nicht (ÄNDERN!) • INTERNAL_API_KEY: Für Worker-Zugriff (ÄNDERN!) • LICENSE_ENCRYPTION_KEY: AES-256 Key (NIEMALS ändern!) • TWO_FACTOR_ENCRYPTION_KEY: Separater Key für 2FA-Secrets • ADMIN_INITIAL_PASSWORD: Initiales Admin-Passwort (nur beim 1. Start) • REDIS_URL: redis://:PASSWORD@redis:6379 (mit Passwort!) • SEED_DATABASE: true bei erstem Start • SESSION_MAX_HOURS, ACCESS_TOKEN_EXPIRY_MINUTES, REFRESH_TOKEN_EXPIRY_MINUTES • ENABLE_FIPS, PBKDF2_ITERATIONS, UV_THREADPOOL_SIZE • TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY (Cloudflare Bot Protection) • FILE_UPLOAD_RATE_LIMIT, EMAIL_AUTO_CREATE_USER_DAILY_LIMIT In der Oberfläche konfiguriert: • Firmenname und Application-URL (Admin-Center → System → Allgemein) Health-Check: • node fetch http://localhost:3000/api/health/live • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 300s (für Migrations + Seed) Volumes: • uploads:/app/uploads (Attachments) • quarantine:/app/quarantine (Infected Files) • sourcemaps:/opt/sourcemaps:ro (liest Frontend-Sourcemaps für Error-Tracking) Depends-On: • db (service_started) • redis (service_started)
4. db (PostgreSQL 17)
Zweck: Persistente Datenbank aller Anwendungsdaten Technologie: • PostgreSQL 17 Alpine (kleines Image) • Kein externer Port (nur expose: 5432, nur intern erreichbar) Environment-Variables: • POSTGRES_USER: helpdesk_user (Main User, Full-Access) • POSTGRES_PASSWORD: supersecretpassword (ÄNDERN!) • POSTGRES_DB: helpdesk_db • JOBWORKER_DB_PASSWORD: Restricted User-Password (ÄNDERN!) • READONLY_DB_PASSWORD: Read-Only User-Password (ÄNDERN!) DB-User Hierarchie: 1. helpdesk_user (Main) └─ Full-Access, Migrations, Schema-Changes └─ Nur Backend nutzt diesen User 2. helpdesk_jobworker (Restricted) └─ Nur CronJob, JobExecution, WorkerInstance └─ Principle of Least Privilege └─ Job-Worker nutzt diesen User 3. helpdesk_readonly (Read-Only) └─ SELECT auf alle Tabellen └─ Für Reporting/Analytics & Report-Generator Health-Check: • pg_isready -U helpdesk_user -d helpdesk_db • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 30s Init-Scripts: • Im Image eingebacken (01-create-users.sh erstellt restricted Users)• Laufen nur beim ERSTEN Start (wenn DB leer) Volumes: • postgres_data:/var/lib/postgresql/data (Persistenz!) Restart-Policy: • unless-stopped
5. redis (Cache & Queue)
Zweck: Cache, PubSub (Workflows), Queues (E-Mail/Notifications), Locks
Technologie:
• Redis 8.6 Alpine
• Kein externer Port (nur expose: 6379, nur intern erreichbar)
• Appendonly-Mode (Persistence)
• Passwort-geschützt (--requirepass)
Use-Cases:
• Cache: Session-Cache, Query-Cache
• PubSub: workflow:step:complete Events
• Queue: Email-Queue, Notification-Queue, Report-Queue
• Locks: Distributed Locks (CronJobs, Workflows)
• Audit-Fallback: Bei DB-Ausfall (7 Tage TTL)
Command:
• redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
Health-Check:
• redis-cli -a ${REDIS_PASSWORD} --no-auth-warning ping | grep PONG
• Interval: 15s, Timeout: 5s, Retries: 3
• Start-Period: 10s
Volumes:
• redis_data:/data (AOF-Files)
Restart-Policy:
• unless-stopped
6. email-worker (Email Processing)
Zweck: E-Mail-Verarbeitung im Hintergrund (SMTP-Versand, Templates, Mehrsprachigkeit) Technologie: • Node.js 20+ • BullMQ (Redis-Queue) • Nodemailer (SMTP-Client) • Handlebars (Templates) • Health-Port: 3005 Architektur: • KEINE Datenbank-Verbindung • Alle Daten via Backend Internal-API • SMTP-Config via Backend-API • Template-Rendering via Backend-API Environment-Variables: • REDIS_URL: redis://:PASSWORD@redis:6379 (mit Passwort) • BACKEND_URL: http://backend:3000 • INTERNAL_API_KEY: Authentifizierung für Backend-API • LICENSE_ENCRYPTION_KEY: Für License-Validierung • HEALTH_PORT: 3005 • EMAIL_ACCENT_COLOR, EMAIL_APP_NAME, EMAIL_APP_URL (Branding) • EMAIL_FOOTER_TEXT, EMAIL_LAYOUT_ENABLED (Layout) • FRONTEND_URL (Für Links in E-Mails) • EMAIL_INBOUND_RATE_LIMIT_PER_MINUTE (DDoS-Schutz) • EMAIL_INBOUND_RATE_LIMIT_PER_SENDER_PER_HOUR • EMAIL_INBOUND_MAX_SIZE_MB (Max E-Mail-Größe) Health-Check: • node dist/healthcheck.js • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 30s Depends-On: • redis (service_healthy) • backend (service_healthy) Restart-Policy: • unless-stopped
7. job-worker (CronJobs & Automation)
Zweck: Geplante Jobs (28 Action-Types), SLA-Monitor, Asset-Clustering Technologie: • Node.js 20+ • node-cron (Scheduler) • BullMQ (Queue) • Port 3001 (Health-Check) Architektur: • RESTRICTED DB-User: helpdesk_jobworker • Zugriff nur auf: CronJob, JobExecution, WorkerInstance • Alle anderen Daten via Backend Internal-API • Principle of Least Privilege Environment-Variables: • DATABASE_URL: postgresql://helpdesk_jobworker:PASSWORD@db/helpdesk_db • REDIS_URL: redis://:PASSWORD@redis:6379 • BACKEND_URL: http://backend:3000 • INTERNAL_API_KEY: Für Backend-API • INSTANCE_ID: Optional (Auto-Generated für Multi-Instance) Multi-Instance Support: • Distributed Locks via Redis (verhindert Doppel-Execution) • Heartbeat alle 15s (Eintrag verfällt nach 30s) • Execution-Tracking (executedBy-Field) Health-Check: • node fetch http://localhost:3001/health/live • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 30s Security-Hardening: • read_only: true, tmpfs: /tmp • security_opt: no-new-privileges:true • cap_drop: ALL • Keine Volumes (Prisma-Schema im Image enthalten) Depends-On: • db (service_healthy) • redis (service_healthy) • backend (service_healthy) ← Wichtig! Backend muss DB-Grants setzen Restart-Policy: • unless-stopped
8. workflow-engine (Business Process Management)
Zweck: Workflow-Ausführung (8 Node-Typen), Step-Orchestrierung, Timer-Events Technologie: • Node.js 20+ • Redis PubSub (workflow:step:complete) • Port 3003 (Internal API) Architektur: • KEINE Datenbank-Verbindung • Alle Daten via Backend Internal-API • Event-Driven (Redis PubSub) Environment-Variables: • REDIS_URL: redis://:PASSWORD@redis:6379 (REQUIRED!) • REDIS_PASSWORD: Für PubSub-Verbindung • BACKEND_URL: http://backend:3000 • INTERNAL_API_KEY: Für Backend-API • PORT: 3003 (Internal-API) • SLA_CHECK_INTERVAL_MINUTES: 5 (Timer-Checker) • RECOVERY_STUCK_THRESHOLD_MINUTES: 10 (Startup-Recovery) • CIRCUIT_BREAKER_THRESHOLD: 5 (Fehler-Schwelle) Health-Check: • node fetch http://localhost:3003/health/live • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 30s Kubernetes-Style Probes: • /health/live (Liveness: Process running?) • /health/ready (Readiness: Redis connected?) • /health (Startup: Full check) Depends-On: • redis (service_healthy) ← Pflicht: Die Workflow-Engine braucht Redis • backend (service_healthy) Restart-Policy: • unless-stopped
9. notification-worker (External Notifications)
Zweck: Notifications an externe Dienste (Webex, Teams, WebPush) Technologie: • Node.js 20+ • BullMQ (Redis-Queue) • WebPush (Browser-Notifications) • Webex/Teams-SDKs • Health-Port: 3006 Architektur: • KEINE Datenbank-Verbindung • Alle Daten via Backend Internal-API • User-Preferences via API • Adapter-Configs via API Environment-Variables: • REDIS_URL: redis://:PASSWORD@redis:6379 • REDIS_PASSWORD: Redis-Passwort • BACKEND_URL: http://backend:3000 • INTERNAL_API_KEY: Für Backend-API • HEALTH_PORT: 3006 • LOG_LEVEL: info (debug, info, warn, error) Health-Check: • node dist/healthcheck.js • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 30s Depends-On: • redis (service_healthy) • backend (service_healthy) Restart-Policy: • unless-stopped
10. report-generator (Reports & CSV Export)
Zweck: Report-Generierung, CSV-Export, Dashboard-Daten Technologie: • Node.js 20+ • BullMQ (Redis-Queue) • Prisma ORM (Read-Only DB-Zugriff) • Port 3004 Architektur: • READ-ONLY DB-User: helpdesk_readonly • Kann keine Daten verändern (nur SELECT) • BullMQ für asynchrone Report-Jobs • Multi-Instance-fähig (BullMQ-basiert) Environment-Variables: • DATABASE_URL: postgresql://helpdesk_readonly:PASSWORD@db/helpdesk_db (Read-Only!) • REDIS_URL: redis://:PASSWORD@redis:6379 • BACKEND_URL: http://backend:3000 • INTERNAL_API_KEY: Für Backend-API • PORT: 3004 • COMPANY_NAME: Firmenname in Report-Exporten (unabhängig vom Firmennamen im Admin-Center) • CSV_DELIMITER: ";" (deutsch), "," (international), "tab" Health-Check: • node fetch http://localhost:3004/health/live • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 30s Security-Hardening: • read_only: true, tmpfs: /tmp • security_opt: no-new-privileges:true • cap_drop: ALL • Keine Volumes (Prisma-Schema im Image enthalten) Depends-On: • db (service_healthy) • redis (service_healthy) • backend (service_healthy) Restart-Policy: • unless-stopped
11. clamav (Virus Scanner Daemon)
Zweck: Virenscan-Daemon (ClamAV-Engine) Technologie: • ClamAV 1.5.1 (Official Image) • Freshclam (Auto-Update Virus-Definitions) • Port 3310 (Internal, nicht exposed) Environment-Variables: • FRESHCLAM_DAEMON: yes (Auto-Updates) • CLAMD_DAEMON: yes (Daemon-Mode) • FRESHCLAM_CHECKS: 24 (Updates alle 60min) Volumes: • clamav_data:/var/lib/clamav (Virus-Definitions) • uploads:/app/uploads:ro (Read-Only Upload-Access!) Security-Hardening: • security_opt: no-new-privileges:true • cap_drop: NET_RAW, SYS_ADMIN, MKNOD • Read-Only Upload-Access (kann Files nicht ändern) Resource-Limits: • Memory: 2GB Limit, 512MB Reservation • CPU: 2.0 Cores Health-Check: • clamdcheck.sh (Official ClamAV-Script) • Interval: 60s, Timeout: 10s, Retries: 3 • Start-Period: 180s (3min für Virus-DB-Load) Logging: • max-size: 10MB, max-file: 3 (Rotation) Restart-Policy: • unless-stopped
12. av-worker (Virus Scan Worker)
Zweck: Pollt neue Uploads, sendet zu ClamAV, updatet Status via Backend-API Technologie: • Node.js 20+ • ClamAV-Client (TCP 3310) • Health-Port: 3007 Isolierung: • Kein Datenbankzugriff • Kein Zugriff auf die Upload-Dateien (übergibt ClamAV nur den Dateipfad) • Kommuniziert nur via: - TCP mit ClamAV (Port 3310) - HTTP mit Backend (Internal-API) Environment-Variables: • BACKEND_URL: http://backend:3000 • INTERNAL_API_KEY: Für Backend-API • CLAMAV_HOST: clamav (DNS) • CLAMAV_PORT: 3310 • HEALTH_PORT: 3007 • REDIS_URL: redis://:PASSWORD@redis:6379 • SCAN_POLL_CRON: */10 * * * * * (alle 10 Sekunden) • SCAN_BATCH_SIZE: 5 (Max parallele Scans) • SCAN_TIMEOUT_MS: 120000 (2 min pro Scan) Härtung: • read_only: true (Container-Filesystem read-only!) • tmpfs: /app/tmp + /tmp (für temporäre Dateien) • security_opt: no-new-privileges:true • cap_drop: ALL (Alle Linux-Capabilities entfernt!) Resource-Limits: • Memory: 1.5GB Limit, 256MB Reservation • CPU: 1.0 Core • NODE_OPTIONS: --max-old-space-size=256 Health-Check: • node dist/healthcheck.js • Interval: 15s, Timeout: 5s, Retries: 3 • Start-Period: 30s Depends-On: • clamav (service_healthy) ← Wartet auf ClamAV-Ready • backend (service_healthy) Restart-Policy: • unless-stopped
Networking
Projekt-Netzwerk: • Docker Compose erstellt automatisch ein eigenes Bridge-Netzwerk für den Stack • Alle Container können sich via DNS erreichen • DNS-Namen = Service-Namen (z. B. "backend", "db", "redis") Service-Discovery (Beispiele): Traefik → Frontend: http://frontend:80 Traefik → Backend: http://backend:3000 Backend → DB: postgresql://helpdesk_user@db:5432/helpdesk_db Backend → Redis: redis://:PASSWORD@redis:6379 Worker → Backend: http://backend:3000 AV-Worker → ClamAV: tcp://clamav:3310 Report → DB: postgresql://helpdesk_readonly@db:5432/helpdesk_db Exposed Ports (Host → Container): 80:80 → Traefik (HTTP → HTTPS Redirect) 443:443 → Traefik (HTTPS) 3000:3000 → Backend API Internal-Only Ports (nicht exposed): 80 → Frontend (nur via Traefik) 5432 → PostgreSQL (nur intern) 6379 → Redis (nur intern) 3001 → Job-Worker (Health) 3003 → Workflow-Engine 3004 → Report-Generator 3005 → Email-Worker (Health) 3006 → Notification-Worker (Health) 3007 → AV-Worker (Health) 3310 → ClamAV (nur für av-worker)
Depends-On & Startup-Reihenfolge
Startup-Reihenfolge:
1. db + redis (starten parallel, keine Dependencies)
│
├─ db: PostgreSQL startet
│ └─ Init-Scripts laufen (01-create-users.sh)
│
└─ redis: Redis startet mit AOF-Persistence + Passwort
2. backend (wartet auf db + redis)
│
├─ Verbindet zu db + redis
├─ Prisma-Migrations laufen (automatisch)
├─ DB-Grants für Restricted-Users
├─ Seed-Data (falls SEED_DATABASE=true)
└─ Health-Check: /api/health/live → HEALTHY
3. Worker + Report-Generator (warten auf backend.service_healthy)
│
├─ email-worker: Verbindet zu redis, Backend-API
├─ job-worker: Verbindet zu db (restricted), redis, Backend-API
├─ workflow-engine: Verbindet zu redis, Backend-API
├─ notification-worker: Verbindet zu redis, Backend-API
└─ report-generator: Verbindet zu db (readonly), redis, Backend-API
4. clamav (startet parallel)
│
├─ Lädt Virus-Definitions (kann 2-3 Minuten dauern)
└─ Health-Check: clamdcheck.sh → HEALTHY
5. av-worker (wartet auf clamav.service_healthy + backend.service_healthy)
│
├─ Verbindet zu ClamAV (TCP 3310)
├─ Verbindet zu Backend-API
└─ Startet Polling (alle 10s)
6. frontend (wartet auf backend.service_healthy)
│
├─ NGINX startet
└─ Health-Check: wget http://localhost:80/health → HEALTHY
7. traefik (wartet auf backend + frontend healthy)
│
├─ Lädt Konfiguration (traefik.yml + dynamic.yml)
└─ Health-Check: traefik healthcheck --ping → HEALTHY
Kritischer Pfad:
db → backend → [workers, frontend] → traefik
→ av-worker
Gesamt-Startup-Zeit:
• Ohne ClamAV: ~45-60 Sekunden
• Mit ClamAV: ~3-4 Minuten (Virus-DB-Load)
Health-Checks
| Service | Test | Interval | Start-Period |
|---|---|---|---|
traefik | traefik healthcheck --ping | 15s | 10s |
frontend | wget -qO- http://localhost:80/health | 15s | 10s |
backend | node fetch /api/health/live | 15s | 300s |
db | pg_isready -U helpdesk_user -d helpdesk_db | 15s | 30s |
redis | redis-cli -a PASSWORD ping | grep PONG | 15s | 10s |
email-worker | node dist/healthcheck.js | 15s | 30s |
job-worker | node fetch /health/live (port 3001) | 15s | 30s |
workflow-engine | node fetch /health/live (port 3003) | 15s | 30s |
notification-worker | node dist/healthcheck.js | 15s | 30s |
report-generator | node fetch /health/live (port 3004) | 15s | 30s |
clamav | clamdcheck.sh | 60s | 180s |
av-worker | node dist/healthcheck.js | 15s | 30s |
Volumes & Persistence
Named Volumes
| Volume | Zweck | Größe (geschätzt) |
|---|---|---|
postgres_data | PostgreSQL-Daten (KRITISCH!) | 10-100GB (je nach Daten) |
redis_data | Redis AOF-Files (Cache/Queue) | 100MB-1GB |
uploads | User-Uploads (Attachments) | 1GB-1TB (je nach Nutzung) |
quarantine | Infizierte Files (isoliert) | < 100MB (selten) |
clamav_data | Virus-Definitionen (täglich Updates) | 500MB-1GB |
sourcemaps | Frontend-JS-Sourcemaps, die fünf neuesten Releases (Backend liest read-only für Error-Tracking) | 100-500MB (5 Releases) |
Volume-Backup-Strategie
# CRITICAL: postgres_data (daily!)
docker run --rm \
-v eviworx_postgres_data:/data \
-v /backup:/backup \
alpine tar czf /backup/postgres-$(date +%Y%m%d).tar.gz /data
# IMPORTANT: uploads (weekly)
docker run --rm \
-v eviworx_uploads:/data \
-v /backup:/backup \
alpine tar czf /backup/uploads-$(date +%Y%m%d).tar.gz /data
# Optional: redis_data, clamav_data (can be rebuilt)
Hinweis: Siehe Installation-Dokumentation für vollständige Backup-Strategie mit pg_dump (bessere Option als Volume-Backup).
Commands
Startup
# Pull images and start all containers
docker compose pull
docker compose up -d
# Only specific services
docker compose up -d backend db redis
Shutdown
# Stop all containers (volumes remain)
docker compose down
# With volume deletion (CAUTION!)
docker compose down -v
# Only stop (do not remove)
docker compose stop
Troubleshooting
# Check logs
docker compose logs backend
docker compose logs -f backend # Follow mode
# Health status
docker compose ps
docker inspect eviworx-backend | grep -A 5 Health
# Restart container
docker compose restart backend
# Into container shell
docker exec -it eviworx-backend sh
Production-Checklist
- Alle Secrets geändert (JWT_SECRET, SHARE_SECRET, INTERNAL_API_KEY, DB-Passwords, REDIS_PASSWORD, LICENSE_ENCRYPTION_KEY, TWO_FACTOR_ENCRYPTION_KEY)
- SEED_DATABASE=false gesetzt (nach erstem Start)
- Resource-Limits für alle Services gesetzt
- Traefik konfiguriert (traefik.yml + dynamic.yml + SSL-Zertifikate)
- SSL-Zertifikate installiert
- Backup-Jobs eingerichtet (postgres_data, uploads)
- Health-Checks konfiguriert
- Log-Aggregation (ELK, Loki)
- Firewall-Regeln (nur 80/443 von außen)
- Cloudflare Turnstile konfiguriert (Bot-Schutz)