Eviworx
Docs

Container-Architektur

Eviworx läuft in 12 Containern unter Docker Compose. Jeder Container hat eine klar abgegrenzte Aufgabe, ist gehärtet (Read-only-Dateisystem, entzogene Capabilities, Nicht-root-Benutzer), hat einen Health-Check und kommuniziert über das Docker-Netzwerk mit den anderen. Traefik ist das API-Gateway und terminiert TLS.

🐳
Funktionen
✓ 12 Production-Container (davon 5 Worker)
✓ API-Gateway mit TLS-Termination (Traefik v3.7)
✓ Härtung (Read-only, cap_drop, Nicht-root)
✓ Health-Checks für alle Container (15–60 s)
✓ Speicher- und CPU-Limits (alle 12 Container)
✓ Least-Privilege-DB (3 PostgreSQL-User)
✓ Worker-Authentifizierung (INTERNAL_API_KEY)
✓ 6 Named Volumes (z. B. postgres_data, uploads)
✓ Horizontal skalierbare Worker (--scale)
✓ Isolierter Virenscan (kein Upload-Zugriff)

Container-Übersicht

Container Port Technologie Verantwortlichkeit
traefik80, 443, 8082Traefik v3.7API-Gateway, TLS-Termination, Routing, Rate-Limiting
frontend80 (internal)Nginx + React 19Web-UI, statische SPA-Files
backend3000Node.js 24 + ExpressREST API, Business-Logic
postgres5432 (internal)PostgreSQL 17Primäre Datenbank
redis6379 (internal)Redis 8Cache, Queues, Pub/Sub
clamav3310ClamAV 1.5.1Virus-Scan Daemon
email-worker3005Node.js 20E-Mail Processing (IMAP/SMTP/Graph API)
notification-worker3006Node.js 20Multi-Channel Notifications
job-worker3001Node.js 24CronJobs, SLA-Monitor
av-worker3007Node.js 24Virus-Scan Koordination
workflow-engine3003Node.js 20Workflow-Execution
report-generator3004Node.js 24Report-Generierung, CSV/PDF Export

Container-Details

1. Traefik (API-Gateway & TLS-Termination)

TRAEFIK CONTAINER

Image: traefik:v3.7.7

Ports:
• 80:80 → HTTP (redirect to HTTPS)
• 443:443 → HTTPS (TLS 1.2+)
• 8082 (internal) → Ping endpoint (health check)

Verantwortlichkeiten:
• API-Gateway: Single entry point for all external traffic
• TLS-Termination: TLS 1.2/1.3 with strong cipher suites
• Routing:
  /api/* → http://backend:3000
  /socket.io/* → http://backend:3000 (WebSocket upgrade)
  / → http://frontend:80 (SPA fallback)
• Rate-Limiting: 100 req/s average, burst 200
• Security-Headers: HSTS, CSP, X-Frame-Options, X-Content-Type-Options
• Internal interfaces between the services not reachable from outside (middleware)

Konfiguration:
• traefik/traefik.yml → Static configuration (entrypoints, providers, ping, forwardedHeaders.trustedIPs)
• traefik/dynamic.yml → Dynamic configuration (routers, middlewares, TLS)

Externer Reverse Proxy:
• Bei Betrieb hinter externem Proxy: forwardedHeaders.trustedIPs in traefik.yml konfigurieren• Zusätzlich TRUSTED_PROXIES in .env setzen (siehe Installation)
Volumes:
• ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
• ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
• ./certs/cert.pem:/etc/traefik/ssl/cert.pem:ro
• ./certs/cert.key:/etc/traefik/ssl/cert.key:ro

Health-Check:
test: ["CMD", "traefik", "healthcheck", "--ping", "--ping.entrypoint=ping"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s

Dependencies:
• backend (service_healthy)
• frontend (service_healthy)

Security:
• security_opt: no-new-privileges:true
• TLS 1.2+ only (no SSLv3, TLS 1.0, TLS 1.1)
• Strong ciphers: ECDHE-*, DHE-RSA-AES*, ChaCha20
• HSTS: max-age=31536000; includeSubDomains
• CSP: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
• X-Frame-Options: SAMEORIGIN
• X-Content-Type-Options: nosniff

Logging:
• JSON format access logs
• Structured request/response logging

Restart: unless-stopped

2. Frontend (Nginx + React SPA)

FRONTEND CONTAINER

Image: nginx:stable-alpine (production)
Build: node:24-alpine (build stage)

Stack:
• React 19.2.0
• Vite 7.2.7 (build tool)
• TanStack Query v5 (data fetching)
• Socket.io Client v4.8.1 (real-time)
• Tailwind CSS v4 (styling)
• Radix UI (components)

Ports:
• 80 (internal only, exposed to Traefik)

Verantwortlichkeiten:
• Serves static SPA files via Nginx
• SPA-Fallback: App-Routen → index.html
• Note: Reverse proxy, TLS, and security headers handled by Traefik

Auslieferung & Caching:
• Build-Dateien unter /_app/ (gehashte Namen): public, max-age=31536000, immutable
• Fehlende Datei unter /_app/ → 404 (kein HTML-Fallback)
• /sw.js, /manifest.webmanifest → no-cache
• index.html und App-Routen → no-cache, must-revalidate
• Sourcemaps (.map) werden über HTTP nicht ausgeliefert
• Vorgeschaltete Proxys: /_app/* und /sw.js unverändert durchreichen und nicht zusätzlich cachen

Health-Check:
test: ["CMD", "wget", "-qO-", "http://localhost:80/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s

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 read-only)

Security-Hardening:
• read_only: true, tmpfs: /var/cache/nginx, /var/run, /tmp
• security_opt: no-new-privileges:true
• cap_drop: NET_RAW, SYS_ADMIN, MKNOD

Umgebung:
• TRUSTED_PROXIES (für korrektes Client-IP-Logging hinter externem Proxy)

Dependencies:
Depends on: backend (service_healthy)

Restart: unless-stopped

3. Backend (Node.js Express API)

BACKEND CONTAINER

Image: node:24-slim

Stack:
• Express v5.2.1 (REST API)
• Prisma v7.5.0 (ORM)
• BullMQ v5.71.0 (job queues)
• Socket.io v4.8.3 (real-time updates)
• JWT (jsonwebtoken v9.0.3)
• PBKDF2-SHA512 (FIPS 140-2 kompatibel)
• Pino v10.3.1 (structured logging)
• Sharp v0.34.5 (image processing)
• Web-Push v3.6.7 (push notifications)

Ports:
• 3000:3000 → REST API

Volumes:
• uploads:/app/uploads (user-uploaded files)
• quarantine:/app/quarantine (infected files)
• sourcemaps:/opt/sourcemaps:ro (frontend source maps, read-only)

Dependencies:
• PostgreSQL (db:5432)
• Redis (redis:6379, password-authenticated)

Health-Check:
test: ["CMD", "node", "-e", "fetch('http://localhost:3000/api/health/live').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 3
start_period: 300s  # 5 min (migrations + seed)

Environment:
DATABASE_URL=postgresql://helpdesk_user:***@db:5432/helpdesk_db
JWT_SECRET=*** (CHANGE IN PRODUCTION!)
JWT_SECRET_OLD=*** (for secret rotation, optional)
SHARE_SECRET=*** (REQUIRED — signs public share links, backend won't boot without it!)
INTERNAL_API_KEY=*** (for worker auth)
LICENSE_ENCRYPTION_KEY=*** (AES-256-GCM for license keys)
TWO_FACTOR_ENCRYPTION_KEY=*** (AES-256-GCM for 2FA secrets)
ADMIN_INITIAL_PASSWORD=*** (only used on first run with empty DB)
VAPID_PUBLIC_KEY/PRIVATE_KEY=*** (Web Push)
MAX_FILE_SIZE=104857600 (100MB)
REDIS_URL=redis://:PASSWORD@redis:6379
TURNSTILE_SITE_KEY=*** (Cloudflare Turnstile CAPTCHA)
TURNSTILE_SECRET_KEY=*** (Cloudflare Turnstile Secret)
ENABLE_FIPS=false (FIPS crypto mode)
PBKDF2_ITERATIONS=210000 (PBKDF2-SHA512 iterations)
SESSION_MAX_HOURS=12
ACCESS_TOKEN_EXPIRY_MINUTES=60
REFRESH_TOKEN_EXPIRY_MINUTES=100
IDLE_TIMEOUT_MINUTES=30
COOKIE_SECURE=true (sonst NODE_ENV-abhängig)
GLOBAL_RATE_LIMIT_MAX=2000, AUTH_FAIL_PER_PAIR_MAX=5, REFRESH_FAIL_PER_IP_MAX=60, …
   (13 Rate-Limits, alle ENV-bar — s. Environment-Seite)

Password-Hashing:
• Default: PBKDF2-SHA512 (FIPS 140-2 compatible)
• FIPS mode: ENABLE_FIPS=true (optional FIPS crypto module)

Init-Sequence:
1. Datenbankzustand prüfen (neu oder bestehend)
2. Migrationen anwenden bzw. Schema anlegen
3. Rechte der eingeschränkten DB-User setzen
4. Datenbank befüllen, wenn SEED_DATABASE=true
5. API-Server starten

Restart: unless-stopped

4. PostgreSQL (Database)

POSTGRES CONTAINER

Image: eviworx/db (basierend auf PostgreSQL 17.5 Alpine, Init-Scripts eingebacken)

Ports:
• 5432 (internal only, not exposed externally)

Volumes:
• postgres_data:/var/lib/postgresql/data (persistence)

Init-Scripts (alphabetisch):
• 01-create-users.sh → Create restricted DB users

3 Database-User (Least Privilege):

1. helpdesk_user (Main)
   • Full access to all tables
   • Used by Backend for migrations
   • Password: POSTGRES_PASSWORD (supersecretpassword - CHANGE!)

2. helpdesk_jobworker (Restricted)
   • SELECT, INSERT, UPDATE, DELETE on:
     - CronJob
     - JobExecution
     - WorkerInstance
   • USAGE on all sequences (auto-increment)
   • Password: JOBWORKER_DB_PASSWORD env

3. helpdesk_readonly (Analytics)
   • SELECT only on all tables
   • Used for reporting (report-generator)
   • Password: READONLY_DB_PASSWORD env

Health-Check:
test: ["CMD-SHELL", "pg_isready -U helpdesk_user -d helpdesk_db"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s

Security:
• Principle of Least Privilege (3 users)
• Init scripts read-only mount
• Named volume isolation
• Port not exposed externally (expose only)

Restart: unless-stopped

5. Redis (Cache & Queue System)

REDIS CONTAINER

Image: redis:8.6-alpine

Ports:
• 6379 (internal only, not exposed externally)

Volumes:
• redis_data:/data (AOF persistence)

Persistence:
• Append-Only File (AOF) enabled
• Command: redis-server --appendonly yes --requirepass PASSWORD

Use-Cases:
• Caching (Permissions, Business Hours, Holidays)
• BullMQ Queues (Email, Notifications, Jobs)
• Rate-Limiting (nur API-Keys; die HTTP-Limiter zählen im Backend-Speicher)
• Session Storage
• Pub/Sub (Domain-Events)
• Distributed Locks (Multi-Instance Coordination)

Health-Check:
test: ["CMD-SHELL", "redis-cli -a PASSWORD --no-auth-warning ping | grep PONG"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s

Security:
• Password authentication (--requirepass)
• AOF for data durability
• Port not exposed externally (expose only)

Restart: unless-stopped

6. ClamAV (Antivirus Daemon)

CLAMAV CONTAINER

Image: clamav/clamav:1.5.1

Internal Port: 3310 (clamd TCP socket)

Volumes:
• clamav_data:/var/lib/clamav (virus definitions)
• uploads:/app/uploads:ro (read-only file scanning)

Resource-Limits:
limits:
  memory: 2G
  cpus: '2.0'
reservations:
  memory: 512M

Security-Hardening:
security_opt:
  - no-new-privileges:true
cap_drop:
  - NET_RAW
  - SYS_ADMIN
  - MKNOD

Health-Check:
test: ["CMD", "clamdcheck.sh"]
interval: 60s
timeout: 10s
retries: 3
start_period: 180s  # 3 minutes (load virus defs)

Auto-Update:
• Freshclam daemon enabled
• Checks 24x/day (FRESHCLAM_CHECKS=24 → hourly)
• Downloads latest virus definitions

Logging:
driver: json-file
max-size: 10m
max-file: 3

Restart: unless-stopped

7. Email-Worker (E-Mail Processing)

EMAIL-WORKER CONTAINER

Image: node:20-slim

Ports:
• 3005 (internal, health endpoint)

Stack:
• nodemailer v6.9.7 (SMTP client)
• imap v0.8.19 (IMAP client)
• Microsoft Graph API (Microsoft 365 mailboxes)
• mailparser v3.6.5 (E-Mail parsing)
• BullMQ v5.65.0 (queue consumption)
• Express v4.21.0 (health endpoint)
• Pino v10.3.1 (structured logging)

Verantwortlichkeiten:
• IMAP Polling (check for new emails)
• Microsoft Graph API (Microsoft 365 support)
• Individual mailbox support
• E-Mail-to-Ticket conversion
• Thread-Matching (RFC 822)
• Bounce-Detection
• SMTP Sending (outbound replies)
• Antworten dem bestehenden Ticket zuordnen
• E-Mails verwerfen
• E-Mail-Signaturen
• TLS-Zertifikat des Mailservers prüfen
Dependencies:
• Redis (6379, password-authenticated) - BullMQ queue
• Backend API (3000) - config + status updates

Health-Check:
test: ["CMD", "node", "dist/healthcheck.js"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s

Environment:
REDIS_URL=redis://:PASSWORD@redis:6379
BACKEND_URL=http://backend:3000
INTERNAL_API_KEY=***
HEALTH_PORT=3005
EMAIL_INBOUND_MAX_SIZE_MB=25
EMAIL_ACCENT_COLOR=#3b8f93
EMAIL_APP_NAME=*** (branding)
EMAIL_APP_URL=*** (branding)
EMAIL_FOOTER_TEXT=*** (branding)
EMAIL_LAYOUT_ENABLED=true
EMAIL_INBOUND_RATE_LIMIT_PER_MINUTE=60
EMAIL_INBOUND_RATE_LIMIT_PER_SENDER_PER_HOUR=30

Security:
• Non-root user: nodejs (UID 1001)
• Kein Datenbankzugriff (alle Daten über die interne Backend-API)
• Dumb-init for signal handling

Restart: unless-stopped

8. Job-Worker (Background Jobs & CronJobs)

JOB-WORKER CONTAINER

Image: node:24-slim

Ports:
• 3001 (internal, health endpoint)

Stack:
• Express v5.1.0 (health endpoint)
• Prisma v7.5.0 (restricted DB access)
• BullMQ v5.65.0 (task queue)
• Bottleneck v2.19.5 (rate limiting)
• Opossum v9.0.0 (circuit breaker)
• Pino v10.3.1 (structured logging)

Verantwortlichkeiten:
• CronJob execution (26+ Action-Types)
• SLA monitoring (every 5 minutes)
• Background tasks scheduling
• Worker heartbeat tracking
• Multi-instance coordination

Dependencies:
• PostgreSQL (5432) - RESTRICTED user: helpdesk_jobworker
  - Tables: CronJob, JobExecution, WorkerInstance
• Redis (6379, password-authenticated) - distributed locking
• Backend API (3000) - domain data

Volumes:
• Keine (Prisma-Schema im Image enthalten)
Health-Check:
test: ["CMD", "node", "-e", "fetch('http://localhost:3001/health/live').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s

Environment:
DATABASE_URL=postgresql://helpdesk_jobworker:***@db:5432/helpdesk_db
REDIS_URL=redis://:PASSWORD@redis:6379
BACKEND_URL=http://backend:3000
INTERNAL_API_KEY=***
METRICS_PORT=3001

Security:
• Restricted DB user (no access to tickets/users)
• Non-root user: jobworker
• Circuit breaker for API failures

Multi-Instance:
• Auto-generated INSTANCE_ID
• Distributed locks via Redis
• Worker heartbeat tracking

Restart: unless-stopped

9. Notification-Worker (Multi-Channel Dispatch)

NOTIFICATION-WORKER CONTAINER

Image: node:20-alpine

Ports:
• 3006 (internal, health endpoint)

Stack:
• BullMQ v5.65.0 (queue consumption)
• Express v4.21.0 (health endpoint)
• Pino v10.3.1 (structured logging)
• Axios (HTTP client for Teams/Webex)

Verantwortlichkeiten:
• Process notification:send queue
• Multi-channel dispatch:
  - EMAIL (via Email-Worker)
  - TEAMS (Microsoft Teams Bot Framework)
  - WEBEX (Cisco Webex Bot API)
  - IN_APP/PUSH (via Backend API)
• Attachment support for notifications
• Quiet Hours (notification suppression)

Dependencies:
• Redis (6379, password-authenticated) - BullMQ queue
• Backend API (3000) - notification config

Health-Check:
test: ["CMD", "node", "dist/healthcheck.js"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s

Environment:
REDIS_URL=redis://:PASSWORD@redis:6379
BACKEND_URL=http://backend:3000
INTERNAL_API_KEY=***
LOG_LEVEL=info
HEALTH_PORT=3006

Security:
• Non-root user: nodejs (UID 1001)
• Kein Datenbankzugriff (alle Daten über die interne Backend-API)
• Dumb-init for signal handling

Restart: unless-stopped

10. AV-Worker (Virus-Scan Worker)

AV-WORKER CONTAINER

Image: node:24-alpine

Ports:
• 3007 (internal, health endpoint)

Stack:
• Axios v1.6.2 (HTTP client)
• Express v5.1.0 (health endpoint)
• Node-cron v4.2.1 (polling scheduler)
• Pino v10.3.1 (structured logging)

Verantwortlichkeiten:
• Poll for unscanned attachments (every 10 seconds)
• Initiate ClamAV scans via TCP protocol
• Update Backend with scan results
• Infizierte Dateien an das Backend melden (das Backend verschiebt sie in die Quarantäne)

Dependencies:
• ClamAV (3310) - virus scanning
• Backend API (3000) - scan status updates
• Redis (6379, password-authenticated)

Resource-Limits:
limits:
  memory: 1536M
  cpus: '1.0'
reservations:
  memory: 256M

HÄRTUNG:
read_only: true  # ← Filesystem completely read-only!

tmpfs:
  - /app/tmp:size=64M,mode=1777
  - /tmp:size=64M,mode=1777

security_opt:
  - no-new-privileges:true

cap_drop:
  - ALL  # ← All capabilities dropped!

Wichtig:
• AV-Worker has NO access to the uploads volume!
• Communicates only via:
  1. ClamAV TCP API (sends path, not content)
  2. Backend HTTP API (status updates)

Health-Check:
test: ["CMD", "node", "dist/healthcheck.js"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s

Environment:
BACKEND_URL=http://backend:3000
INTERNAL_API_KEY=***
CLAMAV_HOST=clamav
CLAMAV_PORT=3310
SCAN_POLL_CRON=*/10 * * * * *  # Every 10 seconds (6-field cron)
SCAN_BATCH_SIZE=5
SCAN_TIMEOUT_MS=120000  # 2 minutes per scan
HEALTH_PORT=3007
REDIS_URL=redis://:PASSWORD@redis:6379

Security:
• Non-root user: avworker (UID 1001)
• Read-only filesystem
• No capabilities
• Tmpfs for temporary files only

Logging:
driver: json-file
max-size: 10m
max-file: 3

Restart: unless-stopped

11. Workflow-Engine (Business Process Automation)

WORKFLOW-ENGINE CONTAINER

Image: node:20-alpine

Ports:
• 3003 (internal, API + health probes)

Stack:
• Express v4.18.2 (internal API)
• redis (node-redis) v5.9.0 (Redis Pub/Sub task intake)
• Node-cron v3.0.3 (scheduled tasks)
• Pino v10.3.1 (structured logging)

Verantwortlichkeiten:
• Workflow state machine execution
• 8 Node-Typen (Manual Task, Approval, Automated Action, etc.)
• 7 Actions (Send Email, Webhook, Create Ticket, etc.)
• SLA tracking integration
• Circuit breaker for API resilience

Dependencies:
• Redis (6379, password-authenticated) - state management, Pub/Sub triggers
• Backend API (3000) - workflow config, domain data

Health-Check:
test: ["CMD", "node", "-e", "fetch('http://localhost:3003/health/live').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s

Health-Endpoints:
• /health/live → Liveness probe (process running)
• /health/ready → Readiness probe (Redis connected)
• /health → Full health check (Redis)

Environment:
REDIS_URL=redis://:PASSWORD@redis:6379
BACKEND_URL=http://backend:3000
INTERNAL_API_KEY=***
PORT=3003
SLA_CHECK_INTERVAL_MINUTES=5
CIRCUIT_BREAKER_THRESHOLD=5
CIRCUIT_BREAKER_RESET_MS=30000

Security:
• Non-root user: nodejs (UID 1001)
• Circuit breaker protects against Backend failures
• Dumb-init for signal handling

Restart: unless-stopped

12. Report-Generator (Custom Reports & Export)

REPORT-GENERATOR CONTAINER

Image: node:24-alpine

Ports:
• 3004 (internal, API + health probes)

Stack:
• Express v5.1.0 (REST API)
• Prisma v7.5.0 (ORM, read-only DB access)
• BullMQ v5.65.0 (scheduled reports queue)
• Pino v10.3.1 (structured logging)

Verantwortlichkeiten:
• Custom report query execution
• CSV export generation
• PDF export generation
• Scheduled reports via BullMQ

Datenbank-Zugriff:
• READ-ONLY access (helpdesk_readonly user)
• No write permissions to any table

Dependencies:
• PostgreSQL (5432) - READ-ONLY user: helpdesk_readonly
• Redis (6379, password-authenticated) - BullMQ queue
• Backend API (3000) - configuration, auth

Volumes:
• Keine (Prisma-Schema im Image enthalten)
Health-Check:
test: ["CMD", "node", "-e", "fetch('http://localhost:3004/health/live').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s

Health-Endpoints:
• /health → Full health check
• /health/live → Liveness probe
• /health/ready → Readiness probe

Environment:
DATABASE_URL=postgresql://helpdesk_readonly:***@db:5432/helpdesk_db
REDIS_URL=redis://:PASSWORD@redis:6379
BACKEND_URL=http://backend:3000
INTERNAL_API_KEY=***
PORT=3004
COMPANY_NAME=*** (for report headers)
CSV_DELIMITER=; (configurable: ";", ",", "tab")

Security:
• Non-root user: nodejs (UID 1001)
• Dumb-init for signal handling
• Read-only database access (helpdesk_readonly)

Restart: unless-stopped

Inter-Container Communication

COMMUNICATION FLOW

USER REQUEST FLOW:

User Browser
  ↓ HTTPS (443)
Traefik (API-Gateway, TLS-Termination)
  ├─ /api/* → Backend (3000)
  ├─ /socket.io/* → Backend (3000, WebSocket upgrade)
  └─ /* → Frontend (80, SPA)

Frontend (nginx:80) → static SPA files only

Backend (3000)
  ├─ TCP 5432 → PostgreSQL (helpdesk_user)
  ├─ TCP 6379 → Redis (password-authenticated, cache, queues)
  └─ Publish domain events → Redis Pub/Sub

WORKER COMMUNICATION:

Redis (6379, password-authenticated)
  ├─ BullMQ Queues:
  │   ├─ email:send → Email-Worker
  │   ├─ notification:send → Notification-Worker
  │   ├─ job:execute → Job-Worker
  │   ├─ workflow:execute → Workflow-Engine
  │   └─ report:generate → Report-Generator
  │
  └─ Pub/Sub Events:
      ├─ domain:events → Workers subscribe
      └─ notification:result → Backend

All Workers → Backend API (3000)
  ├─ GET config/domain data
  ├─ POST status updates
  └─ Auth: Bearer INTERNAL_API_KEY

Report-Generator → PostgreSQL (5432)
  └─ READ-ONLY queries (helpdesk_readonly user)

VIRUS-SCAN FLOW:

AV-Worker (polling every 10 seconds)
  ↓ HTTP
Backend API (fetch pending scans)
  ↓ Returns: [{id, path, ...}]
AV-Worker
  ↓ TCP 3310 (clamd protocol)
ClamAV SCAN /app/uploads/file.pdf
  ↓ Returns: CLEAN / INFECTED
AV-Worker
  ↓ HTTP
Backend API (report scan result)
  ↓ Update scan status

E-MAIL FLOW:

Email-Worker (IMAP polling / Microsoft Graph API)
  ↓ IMAP protocol / Graph API
External IMAP server / Microsoft 365 (support@company.com)
  ↓ Parse email
Email-Worker
  ↓ HTTP
Backend API (process inbound email)
  ↓ Create ticket, link attachments
Backend publishes domain event
  ↓ Redis Pub/Sub
Notification-Worker
  ↓ Queue notification:send
Process notification
  ↓ SMTP (via Email-Worker) or Teams Bot Framework / Webex API
Send notification

Security Architecture

Defense-in-Depth

Layer Maßnahme Container
Transport TLS 1.2/1.3, HSTS, starke Cipher-Suites, Rate-Limiting (100 req/s) Traefik
Gateway Security-Header (CSP, X-Frame-Options), interne Schnittstellen von außen gesperrt Traefik
Anwendung JWT-Authentifizierung, RBAC (28+ Module), PBKDF2-SHA512 (FIPS 140-2 kompatibel), Turnstile-CAPTCHA Backend
Worker-Auth INTERNAL_API_KEY (Bearer Token) Alle Worker
Datenbank 3 DB-User mit minimalen Rechten (Least Privilege), Port nicht nach außen freigegeben PostgreSQL
Cache/Queue Passwort-Authentifizierung (--requirepass), Port nicht nach außen freigegeben Redis
Filesystem Read-only-Dateisystem, tmpfs, cap_drop ALL Alle Worker, Report-Generator
Virus-Scan ClamAV mit automatischem Signatur-Update (Freshclam) ClamAV
Isolation Quarantäne-Volume für infizierte Dateien Backend
Reporting DB-User mit reinem Lesezugriff Report-Generator

Container-Hardening-Vergleich

Container Read-Only Non-Root Cap-Drop no-new-priv Resources Health-Check
AV-Worker✅ ALL1.5G / 1C✅ Custom
Email-Worker✅ ALL512M / 1C✅ Custom
Job-Worker✅ ALL1G / 1C✅ HTTP
Notification-Worker✅ ALL512M / 1C✅ Custom
Workflow-Engine✅ ALL512M / 1C✅ HTTP
Report-Generator✅ ALL1.5G / 1C✅ HTTP
Frontend✅ 3 caps256M / 0.5C✅ HTTP
TraefikPartial (ro)256M / 1C✅ Ping
ClamAV✅ 3 caps2G / 2C✅ Script
Backend✅ 3 caps2G / 2C✅ HTTP
PostgreSQL✅ 2 caps2G / 2C✅ pg_isready
Redis✅ 2 caps1G / 1C✅ redis-cli

🔒 Isolierter Virenscan: Der av-worker läuft mit Read-only-Dateisystem und ohne Linux-Capabilities und hat keinen Zugriff auf die Upload-Dateien. Er kommuniziert nur mit ClamAV und der Backend-API.

Named Volumes

Volume Mounted by Zweck
postgres_data postgres PostgreSQL Datenbank-Files
redis_data redis Redis AOF Persistence
uploads backend (rw), clamav (ro) User-Uploaded Attachments
quarantine backend Infizierte Files (Isolation)
clamav_data clamav Virus-Definitions (Freshclam)
sourcemaps frontend (rw), backend (ro) Frontend-JS-Sourcemaps für Error-Tracking, die fünf neuesten Releases (Backend liest read-only)

Startup-Sequence & Dependencies

CONTAINER STARTUP-REIHENFOLGE (12 Container):

1. PostgreSQL (db)
   • Starts first (no dependencies)
   • Loads init scripts from /docker-entrypoint-initdb.d
   • Creates restricted users (jobworker, readonly)
   • Health: pg_isready

2. Redis (redis)
   • Starts in parallel with PostgreSQL
   • Loads AOF file (if present)
   • Password authentication active (--requirepass)
   • Health: redis-cli ping

3. Backend (backend)
   • Depends on: db, redis (service_started)
   • Waits for DB connection
   • Runs:
     ├─ Datenbankzustand prüfen (neu oder bestehend)
     ├─ Migrationen anwenden bzw. Schema anlegen
     ├─ Rechte der eingeschränkten DB-User setzen
     └─ Seed database (if SEED_DATABASE=true)
   • Starts Express server

4. Frontend (frontend)
   • Depends on: backend (service_healthy)
   • Waits until backend health check OK
   • Nginx starts (static SPA files)

5. Traefik (traefik)
   • Depends on: backend (service_healthy), frontend (service_healthy)
   • Waits until backend + frontend healthy
   • Starts API gateway, TLS termination, routing

6. ClamAV (clamav)
   • Starts in parallel (no dependencies)
   • Loads virus definitions (180s startup period)
   • Freshclam updates hourly (FRESHCLAM_CHECKS=24)

7. Email-Worker (email-worker)
   • Depends on: backend (service_healthy), redis (service_healthy)
   • Waits for backend + Redis
   • Starts IMAP/Graph API polling + queue consumption

8. Job-Worker (job-worker)
   • Depends on: backend (service_healthy), redis (service_healthy), db (service_healthy)
   • Waits for all dependencies
   • Registers worker instance
   • Starts heartbeat + CronJob scheduling

9. Notification-Worker (notification-worker)
   • Depends on: backend (service_healthy), redis (service_healthy)
   • Starts notification:send queue consumption

10. AV-Worker (av-worker)
   • Depends on: backend (service_healthy), clamav (service_healthy)
   • Waits for ClamAV ready (180s!)
   • Starts scan polling (every 10 seconds)

11. Workflow-Engine (workflow-engine)
   • Depends on: backend (service_healthy), redis (service_healthy)
   • Starts workflow execution engine

12. Report-Generator (report-generator)
   • Depends on: db (service_healthy), redis (service_healthy), backend (service_healthy)
   • READ-ONLY DB access (helpdesk_readonly user)
   • Executes queued report jobs (the schedule is paced by the job-worker)

GESAMTE STARTUP-ZEIT: ~3-5 Minuten
  • PostgreSQL: ~10s
  • Backend (with migrations): ~30-60s
  • ClamAV (virus defs): ~180s
  • Workers: ~10-15s after backend ready
  • Traefik: ~5s after backend + frontend ready

Resource Planning

Minimum Requirements

Hardware-Empfehlungen für Development, Production und Hochlast stehen unter Skalierung & Hochverfügbarkeit → Ressourcenplanung.

Container-spezifische Resource-Limits

Container RAM-Limit CPU-Limit Reservation
traefik256MB1.064MB
frontend256MB0.532MB
backend2GB2.0512MB
db (PostgreSQL)2GB2.0256MB
redis1GB1.0128MB
email-worker512MB1.0128MB
job-worker1GB1.0128MB
workflow-engine512MB1.0128MB
notification-worker512MB1.0128MB
ClamAV2GB2.0512MB
AV-Worker1.5GB1.0256MB
report-generator1.5GB1.0256MB

💡 ClamAV Memory: Virus-Definitionen benötigen ~200MB+ RAM. 2GB Limit ist notwendig für große Signaturen-Datenbanken.

Monitoring & Observability

Health-Check Summary

Service Typ Endpoint Interval Startup
TraefikPingtraefik healthcheck --ping15s10s
FrontendHTTPwget localhost:80/health15s10s
BackendHTTPlocalhost:3000/api/health/live15s300s
PostgreSQLCMD-SHELLpg_isready -U helpdesk_user -d helpdesk_db15s30s
RedisCMD-SHELLredis-cli -a PASSWORD ping | grep PONG15s10s
ClamAVScriptclamdcheck.sh60s180s
Email-WorkerCustomnode dist/healthcheck.js15s30s
Job-WorkerHTTPlocalhost:3001/health/live15s30s
Notification-WorkerCustomnode dist/healthcheck.js15s30s
AV-WorkerCustomnode dist/healthcheck.js15s30s
Workflow-EngineHTTPlocalhost:3003/health/live15s30s
Report-GeneratorHTTPlocalhost:3004/health/live15s30s

Health-Endpoints

# Job-Worker Health Endpoints
GET http://localhost:3001/health/live   # Liveness
GET http://localhost:3001/health        # Full health

# Workflow-Engine Health Endpoints
GET http://localhost:3003/health/live   # Liveness
GET http://localhost:3003/health/ready  # Readiness
GET http://localhost:3003/health        # Full health

# Report-Generator Health Endpoints
GET http://localhost:3004/health/live   # Liveness
GET http://localhost:3004/health/ready  # Readiness
GET http://localhost:3004/health        # Full health

Structured Logging (JSON)

Alle Eviworx-eigenen Container loggen im strukturierten JSON-Format via Pino v10. Die Logs enthalten Distributed-Tracing-Felder (traceId, spanId, correlationId) und sind direkt kompatibel mit Elasticsearch/ELK, Loki, Datadog und anderen JSON-Log-Aggregatoren.

Container Log-Format Rotation Tracing-Felder
BackendJSON (Pino)-traceId, spanId, correlationId, requestId, sourceService
Job-WorkerJSON (Pino)-traceId, spanId, correlationId
Email-WorkerJSON (Pino)-traceId, spanId, correlationId
Notification-WorkerJSON (Pino)-traceId, spanId, correlationId
Workflow-EngineJSON (Pino)-traceId, spanId, correlationId
AV-WorkerJSON (Pino)10MB x 3traceId, spanId, correlationId
Report-GeneratorJSON (Pino)-traceId, spanId, correlationId
TraefikJSON (Access-Log)-X-Request-ID, User-Agent
ClamAVNatives Format10MB x 3-
PostgreSQLNatives Format--
RedisNatives Format--
// Beispiel: Backend Log-Eintrag mit Distributed Tracing{
  "level": 30,
  "time": 1773740483502,
  "service": "backend",
  "traceId": "c5d1c321ad743f0abd05d55967adf67d",
  "spanId": "3fcb07029f32d740",
  "correlationId": "c5d1c321ad743f0abd05d55967adf67d",
  "requestId": "b36f4176-0e5b-42d7-8e17-772c0929e4d8",
  "sourceService": "traefik",
  "method": "GET",
  "url": "/api/health/live",
  "status": 200,
  "durationMs": 1,
  "msg": "HTTP request"
}

📝 Monitoring-Integration: Die JSON-Logs können direkt an Elasticsearch, Loki oder andere Aggregatoren gestreamt werden. Die traceId/correlationId ermöglicht Request-Tracking über alle Container-Grenzen hinweg — z.B. von Traefik über Backend bis zum Worker.

Production Best Practices

  1. Secret-Management: Secrets in der .env halten (nicht im Git) oder aus einem Orchestrator (z. B. Kubernetes Secrets) als Umgebungsvariablen übergeben
  2. Resource-Limits: Bereits für alle 12 Container vorkonfiguriert (deploy.resources) — bei Bedarf an die eigene Hardware anpassen
  3. Health-Checks: Alle 12 Container haben Health-Checks konfiguriert
  4. Logging: Zentrales Logging-System (ELK, Loki) für Production
  5. Backup: Regelmäßige Backups von postgres_data, uploads (siehe Docker Compose Details)
  6. Scaling: Worker lassen sich horizontal skalieren (siehe Skalierung)
  7. Security: Alle Secrets MÜSSEN geändert werden (JWT_SECRET, DB-Passwords, API-Keys, Redis-Password)
  8. Network: Docker Compose legt für den Stack ein eigenes Netzwerk an; von außen per Firewall nur die Ports 80/443 freigeben
  9. Updates: ClamAV aktualisiert sich selbst (Freshclam), andere Container: manuell

Verwandte Dokumentation