Eviworx
Docs

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
traefiktraefik healthcheck --ping15s10s
frontendwget -qO- http://localhost:80/health15s10s
backendnode fetch /api/health/live15s300s
dbpg_isready -U helpdesk_user -d helpdesk_db15s30s
redisredis-cli -a PASSWORD ping | grep PONG15s10s
email-workernode dist/healthcheck.js15s30s
job-workernode fetch /health/live (port 3001)15s30s
workflow-enginenode fetch /health/live (port 3003)15s30s
notification-workernode dist/healthcheck.js15s30s
report-generatornode fetch /health/live (port 3004)15s30s
clamavclamdcheck.sh60s180s
av-workernode dist/healthcheck.js15s30s

Volumes & Persistence

Named Volumes

Volume Zweck Größe (geschätzt)
postgres_dataPostgreSQL-Daten (KRITISCH!)10-100GB (je nach Daten)
redis_dataRedis AOF-Files (Cache/Queue)100MB-1GB
uploadsUser-Uploads (Attachments)1GB-1TB (je nach Nutzung)
quarantineInfizierte Files (isoliert)< 100MB (selten)
clamav_dataVirus-Definitionen (täglich Updates)500MB-1GB
sourcemapsFrontend-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)