Deployment
Run the Operion image next to PostgreSQL in your own infrastructure
Operion is one container and one PostgreSQL database. Remote node types, if you have them, are containers of their own that Operion reaches over HTTP.
The image
docker pull ghcr.io/operion-flow/operion:0.1Images are published for linux/amd64 from each release tag, as ghcr.io/operion-flow/operion:<version> (such as 0.1.0), :<major>.<minor> (such as 0.1, which follows patch releases) and :latest.
The image's entrypoint is the operion binary and its default command is serve, so docker run ghcr.io/operion-flow/operion:0.1 starts the server and any other arguments replace serve.
The image is built on distroless static: it has CA certificates and time zone data but no shell, and it runs as a non-root user (uid 65532). Files you mount, such as a node types file, must be readable by that user. Release 0.1.0 predates this and is built on Debian slim, running as root.
PostgreSQL
serve needs DATABASE_URL and applies its migrations on start, so a new database needs no setup beyond a user that can create tables. Operion is tested against PostgreSQL 16.
PostgreSQL holds everything: workflows, executions, schedules, pending steps, the event catalog and the remote node types. Back it up like any production database.
Execution retention
Every execution is kept with its node results and trigger data, so without a limit the database only grows. Set EXECUTION_RETENTION (for example 720h for 30 days) and Operion deletes completed and failed executions that finished longer ago than that, together with their pending steps. Running and waiting executions are never deleted. The cleanup runs at start and then at most hourly, in batches of 500 that each take a short lock; with several replicas, they share the batches.
Running several replicas
Run as many replicas as you need against the same database. The worker and the scheduler claim rows with FOR UPDATE SKIP LOCKED, so each step and each schedule firing runs once, and Kafka partitions are split across the consumer group. Any replica can take webhooks, callbacks and API calls, so put them behind one load balancer.
Settings that matter in production
OPERION_API_TOKEN: a long random value. Whoever holds it can act for every tenant; see tenants.OPERION_PUBLIC_URL: the address remote node services reach Operion at, such ashttp://operion:9091inside a cluster. Without it, remote nodes cannot answer later.KAFKA_BROKERS: only when workflows use catalog events or Kafka topics.EXECUTION_RETENTION: how long to keep finished executions, such as720h; by default they are kept forever. See execution retention.
Every setting is listed in configuration.
Exposing it
serve listens on one port, 9091 by default, for everything:
| Paths | Who calls them | Authentication |
|---|---|---|
/workflows, /executions, /catalog, /platform | Your backend | Bearer token |
/webhooks/<tenant>/... | Systems that start workflows | None |
/callbacks/... | Remote node services | The single-use URL |
/livez, /readyz | Your orchestrator | None |
Only the webhooks, and the callbacks when remote node services live elsewhere, need to be reachable from outside your network.
Outbound traffic
httprequest and remote nodes call whatever address their config renders to, and Operion does not filter it. A workflow can reach anything the container can reach: your internal services, localhost, and your cloud's metadata endpoint such as 169.254.169.254.
Decide what Operion may call with your platform's controls, such as a Kubernetes NetworkPolicy, security groups or an egress proxy. Block the metadata endpoint unless a workflow needs it, and allow only the internal services your workflows call.
Health, logs and traces
GET /livezanswers200while the process serves HTTP. Use it for liveness, so a database outage does not restart healthy processes.GET /readyzanswers200when Postgres answers a ping within a second, and503when it does not orserveis shutting down. Use it for readiness, so traffic only reaches instances that can reach their database.- Logs go to stderr as text lines, at the level from
LOG_LEVEL. - Traces are exported over OTLP/HTTP when
OTEL_EXPORTER_OTLP_ENDPOINTorOTEL_EXPORTER_OTLP_TRACES_ENDPOINTis set; the other standardOTEL_*variables apply.
On SIGTERM, serve stops taking requests and lets its background loops finish before it exits. A step interrupted anyway runs again on the next start, so the services your nodes call should accept a repeated request.