Operations
What a running deployment does on its own, and what you need to watch.
The two processes
| Web | Worker | |
|---|---|---|
| Runs | Next.js app, API, editor | Every workflow and node |
| Binds a port | Yes (PORT) | No |
| Health check | /api/health | None — it serves no HTTP |
| Scale to zero | Possible | Never — it polls Redis for jobs |
The worker receives no HTTP traffic ever, so any "sleep when idle" setting will stop it processing work entirely. Free tiers that spin down on inactivity cannot run it.
Health checks
/api/health returns 200 ok. It is liveness only and deliberately does
not touch Postgres or Redis: a platform health check gates whether a deployment
goes live, so probing dependencies turns a transient blip during rollout into a
failed deploy and a rollback loop.
That means a green health check says the web process is up — not that the database is reachable.
Graceful shutdown
On SIGTERM the worker stops accepting new jobs, waits for in-flight ones to
finish, then closes its queue connections. Order matters: the connections are
closed only after the jobs using them have completed.
Give the worker a long stop timeout — 180 seconds is a reasonable starting
point. Node concurrency is 5 workflows and 10 nodes, and AI and HTTP nodes can
run for minutes. Many platforms default the grace period to zero, which turns
SIGTERM straight into SIGKILL and drops running work.
Retention
Nothing expired the database before this existed. The worker runs one sweep daily, on the queue, in two stages:
| Window | Default | Removes |
|---|---|---|
ORCHKA_RETAIN_STEPS_DAYS | 30 days | Step detail and payloads |
ORCHKA_RETAIN_EXECUTIONS_DAYS | 90 days | The execution rows themselves |
Step detail is bulky and short-lived; the run itself is cheap and worth keeping in the history list, so they expire separately. Deleting an execution cascades to its steps, payloads and dead letters.
Two things the sweep never touches:
- Daily statistics (
execution_stat). These have no link to executions precisely so the rollups behind the dashboard outlive the runs they summarise. - Executions with a pending dead letter. Sweeping one would erase an un-triaged failure before anyone saw it. Resolve or discard the dead letter first.
The credential audit log rides the execution window.
Logging
The worker logs structured JSON to stdout via pino. Every line emitted during a
run carries the same executionId, workflowId and nodeId, so investigating
an incident is one filter rather than a grep through interleaved runs.
{"level":30,"time":1788911772997,"workflowId":"cmp...","executionId":"cmt...",
"nodeId":"x7m...","nodeName":"Cron Trigger","msg":"[Node 1/2] Executing Cron Trigger"}LOG_LEVEL controls verbosity (default info).
Node input and output are not logged. They were once, which meant HTTP response bodies and AI output went to stdout on every node of every run. Payloads are visible in the execution viewer instead, where they are access-controlled.
There is no log aggregation built in — the process ships JSON and whatever runs it collects the output.
Redis is not a cache
BullMQ keeps in-flight job state and per-execution step history in Redis; steps are flushed to Postgres on completion and at periodic checkpoints. A Redis that loses data therefore loses running workflows.
- Enable persistence (AOF, or at minimum snapshots). The local compose file sets
appendonly yeswithappendfsync everysec. - Set the eviction policy to
noeviction. On an evicting policy Redis discards job data under memory pressure and runs disappear with nothing in the logs.
Cron schedules survive a Redis wipe: the worker reconciles them from Postgres at boot and re-arms anything missing. Only schedules explicitly stopped stay stopped.
What to watch
Nothing alerts on its own. The two worth wiring up:
- Dead letters. Terminally failed nodes land in a table with a replay UI, and accumulate silently until someone looks. See Execution Reliability.
- Worker liveness. It exposes no endpoint, so absence of log output is the signal that it has stopped.
Backups
Not configured by Orchka. Two things need them:
- Postgres — workflows, credentials and history.
CREDENTIAL_ENCRYPTION_KEY— losing it makes every stored credential permanently unreadable, and there is no rotation path. A database backup without the key is not a restore.