Architecture Overview
Eviworx consists of 12 containers, each with a dedicated role. Backend and workers can run as multiple instances; see Multi-Instance Setup.
Container Overview
| Container | Technology | Function | Ports |
|---|---|---|---|
| traefik | Traefik v3 | API gateway, TLS termination, routing, rate limiting | 80, 443 |
| frontend | React 19+ + NGINX | Single Page Application (SPA) | 80 |
| backend | Node.js + Express | REST API & business logic | 3000 |
| job-worker | Node.js + BullMQ | CronJobs & scheduled tasks (28 action types) | 3001 |
| email-worker | Node.js + BullMQ | Email queue processing (IMAP/SMTP, Graph API) | 3005 |
| notification-worker | Node.js + BullMQ | Multi-channel notifications (Teams Bot Framework, Webex), attachment support, quiet hours | 3006 |
| workflow-engine | Node.js | Business process management (8 node types) | 3003 |
| av-worker | Node.js | Virus scan orchestrator (zero-trust) | 3007 |
| report-generator | Node.js + BullMQ | Custom reports, CSV/PDF export (read-only DB) | 3004 |
| db | PostgreSQL 17 | Primary database (150+ models) | 5432 |
| redis | Redis 8 | Cache, job queue & distributed locks | 6379 |
| clamav | ClamAV | Virus scanner daemon | 3310 |
Architecture Diagram
User (Browser)
|
| HTTPS (443) / HTTP (80)
v
+---------------------+
| TRAEFIK |
| (API Gateway v3) |
| TLS-Termination |
| Rate-Limiting |
+---------------------+
| |
/_app/* | | /api/*
/ (SPA) | |
v v
+-------------+ +-------------+
| FRONTEND | | BACKEND |
| (React SPA) | | (Express) |
| NGINX :80 | | :3000 |
+-------------+ +------+------+
|
+----------+-----------+----------+-----------+
| | | | |
v v v v v
+--------+ +--------+ +--------+ +--------+ +----------+
| Job | | Email | | Notif. | |Workfl. | | Report |
| Worker | | Worker | | Worker | | Engine | | Generator|
| :3001 | | :3005 | | :3006 | | :3003 | | :3004 |
+--------+ +--------+ +--------+ +--------+ +----------+
| | | | |
v v v v v
+--------+ +--------+ +--------+
| AV | | Redis | | DB |
| Worker | | :6379 | | :5432 |
| :3007 | +--------+ +--------+
+---+----+
|
v
+--------+
| ClamAV |
| :3310 |
+--------+
Component Details
Traefik (API-Gateway)
- Technology: Traefik v3
- Purpose: API gateway, TLS termination, routing, rate limiting, CSP headers
- Routing:
/api/*→ Backend (:3000)- Internal interfaces between the services are not reachable from outside (blocked by Traefik)
- Everything else → Frontend (:80)
- Health-Check: Ping Entrypoint
- Database access: None
- External Reverse Proxy: Configure forwardedHeaders.trustedIPs in traefik.yml + TRUSTED_PROXIES in .env (Details)
Frontend (React 19+ + NGINX)
- Technology: React 19.2.0, Vite, Tailwind CSS 4, shadcn/ui
- Purpose: Single Page Application (SPA)
- NGINX: Static file serving, SPA routing; hashed build files live under /_app/ (cache rules)
- Communication: REST API → Backend (Port 3000)
Backend (Node.js + Express)
- Technology: Node.js, Express, Prisma ORM, Zod Validation
- Purpose: Central business logic & API
- APIs:
- REST API (Public) – For frontend & external clients
- Internal API – For worker communication (API-key protected)
- Database access: Full access (user:
helpdesk_user)
Job-Worker (CronJobs & Automation)
- Technology: Node.js, BullMQ
- Purpose: Scheduled jobs & automation actions
- Action types (28, selection):
- Webhook (HTTP calls with SSRF protection)
- Assign Agent/Group
- Create/Update Ticket
- Report Schedule Check (scheduled reports)
- Asset Model Clustering
- SLA Monitor
- Attachment Cleanup, Retention Purge
- Workload Sync
- and more...
- Database access: Restricted (only CronJob tables)
- Communication: Internal API for all domain data
- Multi-Instance: Redis distributed locks
Email-Worker
- Technology: Node.js, BullMQ, Nodemailer
- Purpose: Email queue processing & IMAP polling
- Incoming emails: IMAP polling / Graph API → create tickets
- Outgoing emails: SMTP / Graph API sending via queue
- Database access: None (everything via Internal API)
Notification-Worker
- Technology: Node.js, BullMQ
- Purpose: Multi-channel notifications (Teams Bot Framework, Webex)
- Channels:
- Microsoft Teams (via Bot Framework)
- Webex (via REST API)
- Email (delegates to Email-Worker)
- Features: Attachment support, quiet hours
- Two-tier routing: Backend → Notification-Worker → Channel-specific adapter
- Database access: None (everything via Internal API)
Workflow-Engine
- Technology: Node.js, BullMQ
- Purpose: Business process management
- Node types (8):
- Manual Task – Manual tasks for users
- Approval – Approval steps with auto-approval
- Automated Action – API calls, webhooks
- Notification – Send notifications
- Parallel Gateway – Multiple paths in parallel
- Conditional Branch – If/Else branching
- Data Collection – Collect data (forms)
- Timer Event – Delay or deadline
- Database access: None (everything via Internal API)
- Multi-Instance: Redis locks for step processing
AV-Worker (Virus-Scan-Orchestrator)
- Technology: Node.js
- Purpose: Coordinates virus scans (zero-trust)
- Zero-Trust: The AV-Worker has no access to the files.
- Flow:
- Backend stores file in
/app/uploads - AV-Worker passes only the file path to ClamAV (via TCP)
- ClamAV reads file from own volume
- AV-Worker updates status via Internal API
- Backend stores file in
- Quarantine: Infected files automatically to
/app/quarantine(7 days retention)
Report-Generator
- Technology: Node.js, BullMQ
- Purpose: Custom reports, CSV/PDF export
- Database access: Read-Only (
helpdesk_readonly) - Health-Check:
GET /health/live(Port 3004)
PostgreSQL (Datenbank)
- Version: PostgreSQL 17 Alpine
- Data model: 150+ Prisma-Modelle in ~50 Domains
- Users:
helpdesk_user– Full access (Backend)helpdesk_jobworker– Restricted (only CronJob tables)helpdesk_readonly– Read-only (Report-Generator)
- Volumes:
postgres_data(Persistent data)
Redis (Cache & Queue)
- Version: Redis 8 Alpine
- Authentication: Password-protected (--requirepass)
- Usage:
- Cache: RBAC permissions, session cache, audit-chain fallback
- Job-Queue: BullMQ for all workers (Email, Notification, Jobs)
- Distributed Locks: Coordination between instances
- Persistence: AOF (Append-Only-File) – Queue data persists across restarts
ClamAV (Virus-Scanner)
- Version: ClamAV 1.5 (official image)
- Purpose: Virus scanning daemon
- Signature updates: 24x/day – hourly (Freshclam, FRESHCLAM_CHECKS=24)
- Volume access:
/app/uploads(read-only) - Resources: 2 GB RAM Limit, 2 CPUs
Communication Flows
1. User Request (via Traefik)
User → Traefik (HTTPS :443) → Frontend (React SPA) OR Backend (REST API) → PostgreSQL
↓
Redis (Cache)
2. Worker Communication (Zero Database Access)
Worker → Backend (Internal API) → PostgreSQL
↓
Redis (Job Queue + Locks)
Important: The Email, Notification, Workflow and AV workers have no direct database access; they retrieve all data via the Internal API. The Job-Worker has restricted DB access (only CronJob tables), the Report-Generator read-only access (helpdesk_readonly).
3. Notification Flow (Two-Tier Routing)
Backend → Redis (notification:enqueue Queue)
↓
Notification-Worker → fetches User/Template via Internal API
↓
+---------+---------+
v v v
Email Teams Webex
Worker Bot API
Framework
4. Virus Scan Flow (Zero-Trust)
Backend → Uploads file to /app/uploads → Creates DB entry (status: PENDING)
↓
AV-Worker (polling every 10s) → Fetches pending files via Internal API
↓
AV-Worker sends ONLY file path (NOT content!) to ClamAV via TCP
↓
ClamAV reads file from shared volume /app/uploads (read-only)
↓
ClamAV returns: CLEAN / INFECTED
↓
AV-Worker updates status via Internal API
↓
If INFECTED: Backend moves file to /app/quarantine
Security Architecture
Principle of Least Privilege
| Container | Database Access | Rationale |
|---|---|---|
| Traefik | None | No DB access; blocks internal interfaces from outside |
| Backend | Full (helpdesk_user) |
Central business logic requires full access |
| Job-Worker | Restricted (helpdesk_jobworker) |
Only CronJob tables (CronJob, JobExecution, WorkerInstance) |
| Report-Generator | Read-Only (helpdesk_readonly) |
Read-only access for reports/analytics |
| Email-Worker | None | Data via Internal API – no DB access needed |
| Notification-Worker | None | Data via Internal API |
| Workflow-Engine | None | Data via Internal API |
| AV-Worker | None | Data via Internal API – zero-trust |
Network Isolation
- All containers in same Docker network (isolated)
- Only Traefik exposed (ports 80/443)
- Backend/workers/frontend not directly accessible from outside
- Inter-container communication via DNS (e.g.,
http://backend:3000)
Multi-Instance Setup (High Availability)
Eviworx is designed for active-active deployments. Multiple instances can run in parallel.
Distributed Locks (Redis-based)
- Job-Worker: Lock per CronJob (only one instance executes job)
- Workflow-Engine: Lock per workflow step
- Implementation: Redlock-Algorithm
Worker Heartbeat Monitoring
- Each worker instance registers in the Redis health registry via heartbeat
- Heartbeat every 15 seconds, the entry expires after 30 seconds
- Instances without a heartbeat count as offline; their execution history stays visible
- Stuck jobs redistributed (recovery logic)
Load Balancing
- Traefik: Central load balancer & API gateway
- Backend: Multiple instances behind Traefik
- Worker: Automatic via BullMQ (pull-based)
Data Flow Examples
Example 1: Create Ticket
1. User fills out ticket form (Frontend)
2. Frontend → POST /api/tickets (via Traefik → Backend)
3. Backend:
- Validates input (Zod schema)
- Checks permissions (RBAC)
- Creates ticket in PostgreSQL
- Creates audit-chain event
- Enqueues notification (Redis)
4. Notification-Worker (async):
- Fetches User/Template via Internal API
- Sends email/Teams/Webex
5. Frontend receives response (201 Created)
Example 2: Scheduled Job (CronJob)
1. CronJob defined: "Every day at 9am: Generate Report"
2. Job-Worker (Scheduler):
- Checks all jobs (via DB)
- Finds job with cron expression "0 9 * * *"
- Acquired Distributed Lock (Redis)
3. Job-Worker runs action:
- Action: GenerateReport
- Fetches report data via Internal API
- Generates PDF
- Stores in /app/uploads
- Enqueues email notification
4. Email-Worker:
- Sends report by email
5. Job-Worker updated execution status (SUCCESS)
Example 3: Workflow Execution
1. User starts workflow (e.g. "Onboarding Workflow")
2. Backend:
- Creates workflow instance
- Enqueues start event
3. Workflow-Engine:
- Fetches Instance via Internal API
- Processes step by step
- Step 1: Manual Task → assignee notification
- Step 2: Approval → waits for user input
- Step 3: Automated Action → webhook call
- Step 4: Notification → Teams message
4. On each step:
- Acquired Lock (Redis)
- Updates step status via Internal API
- Release Lock
5. Workflow completed → status: COMPLETED
Volumes & Persistence
| Volume | Used by | Purpose |
|---|---|---|
postgres_data |
db | Persistent database data |
redis_data |
redis | AOF persistence for job queue |
uploads |
backend, clamav | Uploaded files (attachments) |
quarantine |
backend | Infected files (7 days retention) |
clamav_data |
clamav | Virus signatures |
sourcemaps |
frontend (rw), backend (ro) | JS source maps for error tracking, the five newest releases (frontend writes, backend reads) |
Resource Requirements
Minimum (Development)
- CPU: 4 cores
- RAM: 8 GB
- Storage: 20 GB
Recommended (Production)
- CPU: 8 cores
- RAM: 16 GB
- Storage: 100 GB (depending on attachments)
Per-Container Limits (Configurable)
| Container | CPU Limit | Memory Limit |
|---|---|---|
| traefik | 1.0 | 256 MB |
| frontend | 0.5 | 256 MB |
| backend | 2.0 | 2 GB |
| db | 2.0 | 2 GB |
| redis | 1.0 | 1 GB |
| job-worker | 1.0 | 1 GB |
| email-worker | 1.0 | 512 MB |
| notification-worker | 1.0 | 512 MB |
| workflow-engine | 1.0 | 512 MB |
| av-worker | 1.0 | 1.5 GB |
| report-generator | 1.0 | 1.5 GB |
| clamav | 2.0 | 2 GB |
Monitoring & Observability
Health-Checks
- Traefik: Ping Entrypoint (built-in health check)
- Backend:
GET /api/health/live(Liveness probe; readiness is checked by /api/health/ready) - Workflow-Engine:
GET /health/live(Liveness probe)GET /health/ready(Readiness probe, checks Redis)
- Report-Generator:
GET /health/live(Port 3004) - Job-Worker: Health-Endpoint (Port 3001)
- Email-Worker: Health-Endpoint (Port 3005)
- Notification-Worker: Health-Endpoint (Port 3006)
- AV-Worker: Health-Endpoint (Port 3007)
System Status (Admin UI + Health API)
The live state of the entire stack is visible in the admin area under Admin Center → System → System-Status (route /admin/system-status). The tab shows each service as an expandable row with its instances — per instance status, version, uptime, time of the last heartbeat, memory usage, port and the individual checks (database, Redis) including response time. Search and a status filter narrow the list. Impaired units — a mailbox without a poll attempt or a cron job in an error state, for example — appear in a separate table, infrastructure and crypto configuration (e.g. FIPS mode) in a detail card. It is backed by the backend Health API:
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/health | None | Basic check (200 healthy / 503 unhealthy) — for monitoring systems |
GET /api/health/live | None | Liveness probe (process alive) |
GET /api/health/ready | None | Readiness probe (DB + Redis reachable) |
GET /api/health/services | settings.editGeneral (logged-in users only) | Status per service and instance (version, uptime, memory usage, port, individual checks), impaired units plus infrastructure and crypto configuration; data source of the System Status tab. |
Note: Service health comes from a Redis registry: every instance writes a heartbeat every 15 seconds and the entry expires after 30 seconds. A service without a heartbeat shows as offline. /api/health/services requires settings.editGeneral and is reserved for logged-in users — infrastructure topology, ports, instance IDs and crypto configuration are not available through an API key.
Structured Logging (JSON)
All Eviworx-owned containers (Backend, Job-Worker, Email-Worker, Notification-Worker, Workflow-Engine, AV-Worker, Report-Generator) log in structured JSON format via Pino. External containers (Redis, PostgreSQL, ClamAV) use their native log format.
- Log library: Pino v10 (all custom containers)
- Fields per log entry:
level,time,service,msg– Standard fieldstraceId,spanId– Distributed tracing (OpenTelemetry-compatible)correlationId,requestId– Request correlation across container boundariessourceService– Shows which service triggered the request (e.g. av-worker, traefik)
- Integration: Directly compatible with Elasticsearch/ELK, Loki, Datadog, Splunk and other JSON log aggregators
- Traefik access logs: Also in JSON format with X-Request-ID and User-Agent
- Rotation: max. 10 MB per file, 3 files (ClamAV, AV-Worker)
- Access:
docker compose logs -f [service-name]
// Example: Backend HTTP request log{
"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"
}
- ✓ Principle of Least Privilege (DB access)
- ✓ Zero-trust virus scan (AV-Worker without file access)
- ✓ Network isolation (Docker network)
- ✓ SHA-256 hash-chained audit log (tamper-evident)
- ✓ Automatic PII scrubbing in audit logs
- ✓ SSRF-Protection (IP-Range + DNS-Blocking)
- ✓ Container hardening (read-only filesystems, capabilities drop)
- ✓ FIPS 140-2 compatible algorithms (PBKDF2-SHA512, AES-256-GCM)
- ✓ MFA/TOTP authentication
- ✓ Redis password authentication
- ✓ Traefik API gateway (rate limiting, CSP)
Details about each container
API modules with endpoints & examples
Zero-trust, audit chain, RBAC
Multi-instance setup