Operion LogoOperion
Operating

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.1

Images 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 as http://operion:9091 inside 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 as 720h; 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:

PathsWho calls themAuthentication
/workflows, /executions, /catalog, /platformYour backendBearer token
/webhooks/<tenant>/...Systems that start workflowsNone
/callbacks/...Remote node servicesThe single-use URL
/livez, /readyzYour orchestratorNone

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 /livez answers 200 while the process serves HTTP. Use it for liveness, so a database outage does not restart healthy processes.
  • GET /readyz answers 200 when Postgres answers a ping within a second, and 503 when it does not or serve is 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_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is set; the other standard OTEL_* 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.