navar.ch

Compose-native deployment

Command the whole stack, not one container.

Most platforms deploy one image at a time. Real systems are stacks — an API, a worker, a broker, a database. Navarch takes the whole Compose file as the unit of deployment, with versioned revisions and zero-downtime blue/green across all of it.

Secrets it cannot read. Values are sealed to each node's public key. Plaintext never reaches the control plane or its database.

navarch — rollout
$ navarch stack push dev/shop/main ./compose.yaml
  version 7 · digest 98d75411a605 · swappable: api, worker · pinned: cache, db

$ navarch deploy --env dev/shop/main/production
  revision 12 · slot green · placed on dev-node-2

$ navarch wait 4f2c8e1a --for live
  scheduling → starting → healthy → live
  traffic moved to revision 12 · revision 11 superseded · no request dropped
Plate I — a rollout, start to promotion
The cardinality problem

Blue/green changes how many copies run. Some services must not be doubled.

Two copies of an API is the mechanism. Two copies of a database is data loss, and two copies of a scheduler is every periodic job firing twice. So each service declares which it is. Navarch never infers it — the author who has not noticed that blue/green changes cardinality is exactly the one an optional field fails to protect.

rollout: swap

Swappable

Duplicated during a rollout. The new revision comes up beside the old, passes its health check, takes traffic — and only then does the old one go away.

blueapi · worker — rev 11
greenapi · worker — rev 12
rollout: pin

Pinned

Runs once, shared across revisions. Databases, brokers, schedulers — anything whose correctness assumes a single instance, whether or not it writes to disk.

shareddb — one instance, both revisions
sharedcache — one keyspace, one lock
compose.yaml
services:
  api:
    image: ghcr.io/acme/api:1.4.0
    x-composectl:
      rollout: swap        # duplicated during a rollout
      ingress: { port: 8080 }

  cache:
    image: redis:7-alpine
    command: ["redis-server", "--save", "", "--appendonly", "no"]
    x-composectl:
      rollout: pin         # mounts nothing, and still must not be doubled

# Omitting rollout: is a parse error. There is no default, deliberately.
Plate II — the declaration, and the case that makes it necessary
Register

Every rollout is a new revision. Nothing is edited in place.

The deployment table is append-only, so the record of what ran, when, and why it stopped is still there at three in the morning when you need it.

i

Append-only revisions

Rollback promotes an older revision as a new one. History is never rewritten, so it can always be read back.

ii

Health-gated promotion

Traffic moves only once the new revision reports healthy. A bad image fails the rollout and leaves the live one untouched.

iii

Sealed secrets

Encrypted to each node's public key. The control plane stores ciphertext it has no key for; the agent decrypts at container start.

iv

Preview environments

One call creates an ephemeral environment with a generated hostname and inherited secrets. A TTL reaps it — containers, volumes and all.

v

A real fleet

Scored placement across nodes, with each environment bound to the node holding its durable state so a later revision cannot drift off it.

vi

Logs that are never stored

Container output is fetched on demand and buffered in memory. Application logs carry secrets, so none of it is written down.

Passage

Push a Compose file. Watch it become a revision.

The control plane owns placement and writes the desired state. Each node runs a reconciler that converges on it — no cross-node overlay, no shared network, no orchestration cleverness to debug at midnight.

01

Push

The Compose file is parsed once into a normalized spec with a stable digest. An unchanged stack does not manufacture a new version.

02

Validate

Every unsupported directive is rejected loudly, all of them in one pass, so you fix the whole file at once rather than one error per attempt.

03

Place

The scheduler scores the fleet and writes the containers one node should be running.

04

Reconcile

That node's agent brings the revision up against its own Docker daemon and reports what happened.

05

Promote

A healthy revision goes live in one transaction. The router repoints, and only then is the old revision torn down.

Cautionary notes

Rejected loudly, never quietly dropped.

A stack must never run differently than its author expects. Where Navarch cannot honour something, it says so when you push — not by silently changing what your deployment means.

ports: "8080:80"A host port collides between revisions. Declare an ingress and the platform routes to it.
bind mountsA host path is not portable across nodes. Named volumes are created and tracked explicitly.
privileged, cap_add, devicesHost escape if applied. Isolation between tenants is not negotiable.
build:The platform does not build. Pre-build and push, so what ran is what you can reproduce.
swap on a writable volumeTwo revisions writing one filesystem is the exact failure the declaration exists to prevent. Refused outright.
a volume nobody mountsAlmost always a typo — and a typo in storage is expensive to find later.
Stated plainly

What it deliberately does not do.

Each of these is a position with a reason behind it, written down — not a gap for you to find in production.

No automatic failover. Nothing re-homes an environment because its node went quiet. An unreachable node usually comes back, and its agent still holds that environment's desired state.
No durable log storage. Application logs routinely contain secrets. Persisting them would undo what sealing buys.
No cross-node overlay. A deployment is placed whole onto one node. Ingress reaches it by address and published port.
Nodes are not shared between organizations. A node is a trust boundary: one agent holds one decryption identity for everything it hosts.