Orchestration & Deployment
Orchestration & Deployment
Section titled “Orchestration & Deployment”Phrame does not deploy by hand and does not guess what is running. Intent is written down, version-controlled, and distributed; every host converges its own containers to match; and what is actually running is measured and published back — so the system you see is the system you have.
Why it matters
Section titled “Why it matters”Broadcast systems are traditionally commissioned once and then edited in place, by hand, under pressure. The configuration that results lives in the building — in device front panels, in patch sheets, in the memory of whoever was on shift. Nobody can say with confidence what is running, or restore it after a failure.
Phrame treats the whole facility as declared state:
- A commit is a deployment. Configuration lives in GitLab. Every change is authored, reviewable, attributable, and revertible, and the deployment is the same act as the record of it — so the two cannot drift apart.
- The system repairs itself toward intent. Each host continuously converges what it is running onto what it should be running. A container that dies is restarted because reality no longer matches the declaration, not because somebody noticed.
- What is running is reported, not assumed. Nodes publish their actual state back continuously. The difference between intended and actual is a value you can look at, rather than a question you have to go and ask.
- A release can be pinned. The live configuration can be pointed at a git tag, and while it is, nothing can move it — not a push, not the scheduler. Changing a pinned system is a deliberate act.
In one line: describe the system you want, in Git; the platform makes it so, keeps it so, and tells you the truth about it.
How it works
Section titled “How it works”There are two planes of state, both carried over MQTT, and one rule that makes the whole thing legible: Git is the source of truth for desired state.
Desired — what should be running. An operator composes a system in
Architect (or edits the configuration directly). The result is
committed to the phrame-config repository in GitLab. config-sync watches
that repository, validates every file against its JSON schema, and publishes it
to a retained MQTT topic. Retained matters: a service that starts, or
restarts, at any hour receives the last-published configuration immediately,
without anything having to notice it arrived.
Placement — deciding where. Declaring that an instance should exist is not
the same as saying which machine runs it. The
orchestration-reconciler watches the desired
instances and the live node state, picks a node for anything unplaced, and
commits that placement back into Git as an entry in that node’s
node.json. The decision therefore becomes part of the same version-controlled
record as the intent it serves.
Actual — converging and reporting. On every host, the node-controller reads the desired container set for its own node, compares it with what the local Docker daemon is really running, and converges the difference — starting, stopping, and reconfiguring containers. It then publishes what it actually has, alongside a heartbeat and host statistics. Mirrors aggregate those retained topics into snapshots the web UI can read without every browser holding a live subscription.
The loop closes: the actual state that comes back is also what the reconciler uses to decide where the next instance should go, and which nodes are alive enough to receive one.
Two properties worth understanding, because they explain most of the behaviour:
- The control plane is orthogonal to the media plane. Redeploying does not disturb frames already in flight. You can redesign the system around running production.
- Nothing in this loop is a one-shot command. There is no “deploy” that can half-succeed and leave the system in an unknown state. Every participant is continuously comparing intent with reality and closing the gap, so an interrupted change is simply a gap that has not closed yet.
Under the hood
Section titled “Under the hood”The layer is four cooperating pieces, each documented in its own page:
| Component | Decides | Page |
|---|---|---|
config-sync | Turns Git into retained MQTT topics, schema-validated | Configuration & GitOps |
orchestration-reconciler | What containers exist, and where | Placement & scheduling |
node-controller | Makes local Docker match the desired set for its host | The node controller |
| Capability manifests | What Architect is allowed to offer, derived from what was built | Capability manifests |
That split between the third and fourth rows of the loop is the one to hold on to: the reconciler decides what should exist where; the node-controller makes local reality match. Neither reaches into the other’s job, which is why a node can be lost and rejoined without the cluster needing to be told, and why the scheduler can be restarted without disturbing a single running container.
Topic layout. config-sync maps repository paths onto topics under a
configurable prefix (default phrame), publishing with retain: true,
qos: 1:
| Repository path | MQTT topic | Validated against |
|---|---|---|
configs/global/platform.json | config/global | global-config |
configs/nodes/{id}/node.json | nodes/{id}/config | node-config |
configs/platforms/{p}/platform.json | config/platforms/{p}/settings | platform-config |
configs/platforms/{p}/services/{type}/{instance}.json | config/platforms/{p}/services/{type}/{instance} | — |
Anything not matching a known pattern — schemas, Lua scripts, registry.json —
is deliberately ignored rather than published.
Shared vocabulary. @phrame/orchestration-contracts is a small,
dependency-light TypeScript package published to the GitLab npm registry and
consumed by the reconciler and the Architect backend. It owns every
UUID-derived identifier (logical id, instance config path and topic, MQTT
prefix, state topic, output stream id, events topic), the container-spec type,
and the orchestration event payload. It exists so that two services cannot
quietly disagree about what a topic is called — a class of bug that is
invisible in both codebases and only appears in an integrated system.
Related: the Control Plane page covers the wider control surface — RBAC, scripting, observability and the config editor — of which this orchestration loop is the deployment half. Hardware and site layout are covered under Physical Deployment.
