Skip to content

Configuration

All configuration is via environment variables. No config files are required. The engine reads .env if present (via dotenv).

Runtime & Logging

Variable Default Description
NODE_ENV development development, production, or test
DEBUG false Enable verbose validation errors. Also switches deferred retry delays from seconds to milliseconds – intended for tests, never for production
LOG_LEVEL info error, warn, info, debug. Defaults to debug when DEBUG=true
LOG_FORMAT pretty json (production) or pretty (development). Auto-selects json in production
PORT 3030 HTTP server port

HTTP

Variable Default Description
Most routes accept a request body of up to 100 kB. Two routes take bulk payloads from workers
and are raised to 50 MB:
  • POST /blackboard/job/result – job results, which can carry bulk entity updates
  • POST /blackboard/job/log – job execution logs
Variable Default Description
MAX_REQUEST_BODY_SIZE (unset) Replaces the 50 MB limit on the two routes above, e.g. 100mb or a plain byte count. Leave unset to keep the built-in limit

Set this when workers push results larger than 50 MB and get 413 Payload Too Large.

It does not change the 100 kB default

MAX_REQUEST_BODY_SIZE only adjusts the routes that are already raised. Every other route, including workflow publishing, keeps the 100 kB limit and is unaffected by this variable.

A malformed value aborts startup

The value is checked when the engine starts, and anything that is not a byte size stops the boot with an error. This is deliberate: an unparseable limit would otherwise be read as unlimited, so a typo would silently remove the cap instead of setting it.

License

The license is compiled into the image and cannot be changed at runtime – there is no environment variable that grants or upgrades one. To see which license is active and how much of each quota is in use, call GET /license/; see API Reference.

The build-time variables behind it are documented under Docker Deployment.

Database

The engine requires PostgreSQL 15+ and is tested against 17 (the version docker-compose.yml runs).

Variable Default Description
POSTGRES_HOST localhost Database hostname
POSTGRES_PORT 5434 Database port
POSTGRES_USER postgres Database user
POSTGRES_PASSWORD unsafe Database password (override in production)

Change the default password

The default password unsafe is for local development only. Always set a strong password in production deployments.

Database Setup

Run migrations to create the schema:

npx mikro-orm migration:fresh    # development (drops and recreates)
npx mikro-orm migration:up       # production (applies pending migrations)

CMS Connection

The engine communicates with the neops CMS via GraphQL for entity acquisition and locking.

Variable Default Description
NEOPS_CMS_URL http://localhost:8000/graphql CMS GraphQL endpoint
NEOPS_CMS_TOKEN unsafe Authentication token for CMS API (override in production)

CMS dependency

The CMS is required for entity acquisition and locking. Without a CMS connection, workflows that use acquire clauses or seedEntity other than global will fail during the resource discovery phase.

Worker Management

These thresholds control how the engine detects and handles unresponsive workers. The defaults cascade: unreachable (2 min) -> offline (3x = 6 min) -> stuck job (2x offline = 12 min).

Variable Default Description
WORKER_UNREACHABLE_THRESHOLD_MS 120000 (2 min) Time since last heartbeat before worker is marked unreachable
WORKER_OFFLINE_THRESHOLD_MS 360000 (6 min) Time since last heartbeat before worker is marked offline and its jobs are failed
WORKER_OFFLINE_DELETE_THRESHOLD_MS 86400000 (24 h) Time before an offline worker is soft-deleted
WORKER_CLEANUP_ENABLED true Enable automatic deletion of stale workers
WORKER_CLEANUP_INTERVAL_MS 3600000 (1 h) How often to check for stale workers

Workflow Engine

The engine is a single-writer design: one instance drives the epoch machine for a given database. On startup it takes a Postgres session-level advisory lock and waits for any outgoing instance to release it, so a rolling deploy hands over without operator action. If the lock is still held when the timeout expires, the process refuses to start rather than running a second engine against the same database.

Variable Default Description
ENGINE_INSTANCE_LOCK_TIMEOUT_MS 60000 (60 s) How long startup waits for another instance to release the single-instance lock before failing
EPOCH_MAX_PARALLEL_RUNS unset (no cap) Soft cap on how many workflows an epoch locks before it stops accepting new locks
EPOCH_RETENTION_DAYS 30 Age after which completed epochs are pruned; 0 or negative disables pruning
EPOCH_CLEANUP_INTERVAL_MS 3600000 (1 h) How often the epoch retention sweep runs

Never run two engines against one database

Scale the engine vertically, not horizontally. A second instance is refused by the lock; if you deliberately need two engines, give each its own database.

EPOCH_MAX_PARALLEL_RUNS is a soft cap

It bounds sustained concurrency, not each individual epoch. The coordinator checks the cap between events against a count that may already be stale, while the packing itself happens inside concurrent handler transactions that do not consult it — so a burst of workflows reaching lock acquisition together can overshoot (three workflows packed with the cap set to one has been observed). Leave it unset unless you need to throttle sustained load; do not rely on it as a hard “at most N locked at once” guarantee.

Completed epochs are pruned in batches on the EPOCH_CLEANUP_INTERVAL_MS schedule. The latest epoch is never deleted, and neither is any epoch a non-terminal workflow execution is still parked in — so pruning can never disturb work in flight, however aggressive the retention setting.

Blackboard (Job Management)

Variable Default Description
BLACKBOARD_JOB_CHECK_INTERVAL 60000 (1 min) How often the engine runs its job cleanup cycle (offline workers, stuck jobs, never-polled jobs)
BLACKBOARD_STUCK_JOB_TIMEOUT 720000 (12 min) Jobs in POLLED state longer than this are auto-failed (default: 2× WORKER_OFFLINE_THRESHOLD_MS)
BLACKBOARD_PENDING_JOB_TIMEOUT 3600000 (60 min) Jobs in PENDING state that no worker ever polled within this deadline are auto-failed, so a workflow — and the epoch holding it — cannot wait forever for a worker that never comes (default: 5× BLACKBOARD_STUCK_JOB_TIMEOUT). Applies to acquire, execute and rollback jobs alike; a job claimed by a worker while the cleanup runs is left to that worker

Example .env

# Production configuration
NODE_ENV=production
LOG_LEVEL=info
LOG_FORMAT=json

# Database
POSTGRES_HOST=postgres.internal
POSTGRES_PORT=5432
POSTGRES_USER=neops_engine
POSTGRES_PASSWORD=<secure-password>

# CMS
NEOPS_CMS_URL=http://neops-cms.internal:8000/graphql
NEOPS_CMS_TOKEN=<cms-auth-token>