Skip to main content
Version: v5.6.1

Architecture overview

Included from Basic tier

OpsChain is one statically-linked binary (opschain) that is simultaneously the CLI, a local execution engine, the server and the runner agent. The entrypoint selects what gets wired; the engine inside is the same everywhere:

opschain <resource> <verb> … # the CLI, pointed at a server (or local)
opschain run change|workflow … # local execution against a working copy
opschain server start # API + scheduler + embedded GUI + store
opschain runner # execution client: register, poll, run

Because every surface ships in the same binary, everything is release-aligned by construction: the reference documentation (opschain docs, /api/docs), the getting-started guide (opschain learn), the OpenAPI document, the GUI and the embedded database migrations all describe exactly the build that is running.

Ports and adapters — one engine, mode parity​

The engine (changes, workflows, step trees, the action catalog, properties/settings, authorisation) is written once against port interfaces — store, queue, dispatch, executor, log pipeline, blobstore, secrets, identity, git. Each deployment mode swaps adapters, never engine code, so a change or workflow produces the same step tree, the same semantics and the same logs in local mode and against a server. That parity is structural: the engine packages import only ports, and the executor conformance suite runs identically against every wiring.

ConcernLocal modeServer mode
StoreSQLite file in the OpsChain homePostgreSQL (or SQLite for embedded installs)
Dispatchin-processstore-backed lease queue → registered runners
Executorhostexec / containerexecchosen per runner, same contract
Identitysingle userlocal users, tokens, directory providers

Storage​

Persistent state lives in two places: the data store (PostgreSQL or an embedded SQLite file, selected by the shape of the store config key) and the blobstore (a content-addressed directory holding bulk bytes — log segments and artifacts — so they never sit in the OLTP path). Migrations are embedded and run at server start. See storage.

The server and the dispatch plane​

opschain server start composes one process: the API (the /api contract the CLI and GUI consume), the scheduler, the runner and agent RPC planes, and the embedded GUI. Ready steps land in a store-backed lease queue; the dispatch plane orders them by priority lane and fair-share, checks placement against the runner registry, and annotates every queued entry with a satisfiability verdict so unsatisfiable work reads as queued-with-a-reason, never a silent hang. See dispatch and fleet-queue.

CLI / GUI ──HTTPS──▶ ┌───────────────────────────────┐
│ opschain server │
│ API ─ engine ─ scheduler │ ┌────────────┐
│ embedded GUI │ │◀────│ opschain │
│ ▼ │ long│ runner │
│ store (PG/SQLite) blobstore │ poll│ executor │
└───────────────────────────────┘ └────────────┘

Runners and executors​

A runner is an outbound-only execution client: it registers once with a registration token, then long-polls the server for leased work — runners dial in; the server never connects to a runner. Each runner holds one job at a time (up to runner.max_parallel_steps jobs of one pinned run, see dispatch), executes each through the executor contract (step_context.json in, step_result.json out, masked logs streamed back) as a host process (hostexec) or a per-step container (containerexec), so capacity scales by running more runners. See runner, runner-registry and runner-executors.

The step tree, the event spine and chains​

Every change and workflow run compiles into a step tree — stages, changes, waits and fan-outs — and the tree is the execution record: entry verdicts, retries, outputs and skips are recorded on the steps themselves. Every run and step transition, every supervised verb and every plane lifecycle fact lands on the append-only event stream, each event carrying a correlation_id and chain_depth, so any event is a handle into its whole cascade; triggers match that stream and create runs in response, extending the same chain. See orchestration, events and triggers.

Actions and packs​

Work is authored as YAML actions in a git repository (templates pin exact revisions per asset), composed with typed uses: + with: parameters. Action packs are git repositories of reusable actions — script:execute, terraform, ansible — mounted by name and called like any other action. See opschain docs getting-started/2-1-your-first-action and getting-started/2-4-using-packs.

The embedded GUI​

The web GUI ships inside the binary and is served by the server: the estate, runs and activity views, the Operations area (fleet, licence, maintenance, identity, configuration — see gui-operations), and a Documentation section reading this embedded documentation set from /api/docs.

High availability​

Several server replicas run against one PostgreSQL database and one shared blobstore behind any load balancer that honours GET /readyz (no session affinity required): replica heartbeats, shard ownership and a leader lease for cron and maintenance live in the shared store; every run is driven by exactly one replica under a durable lease that a peer takes over when the driver dies; and every step attempt's log is spooled by exactly one replica — the first to receive a chunk of it — with the other replicas forwarding the attempt's chunks there over the replica-to-replica lane (peer_url) and the owner sealing the log into the blobstore when the attempt ends. The operations guide is opschain learn 8.7.

  • Concepts glossary: concepts.
  • Runbooks: opschain docs getting-started/00-README — the learning path.