Skip to content

The ownership boundary

You own your application containers. Onebox owns everything else on the box.

You provide and administer the Linux host, SSH access, and Docker installation. Inside that boundary, the proxy, TLS, networks, release staging, supporting data services, schedules, locks, and the evidence needed to operate them are not your application, and therefore sit on Onebox’s side of the line.

That sentence is the direction, not an inventory. What is owned today is listed on Shipped vs proposed, and the gap between the two is stated rather than blurred.

This is the organising principle behind everything else.

It is why the project file declares intent rather than describing containers. If Onebox owns the runtime, then describing the runtime is Onebox’s job, and asking you to do it as well would mean two descriptions that can disagree.

It is why Compose became a generated artifact. Compose was once an input Onebox read. Under this boundary, reading a file you wrote would mean you own the runtime and Onebox merely operates it — which is the opposite arrangement. See Why Compose is generated.

It is why a database is something you select rather than something you configure. services: {postgres: 17} is sufficient because the image, port, data path, health check and credential all sit on Onebox’s side of the line.

“Onebox owns everything else on the box” is only meaningful if exactly one application is doing the owning. Two Onebox applications on one host would both believe they own the proxy, the shared network, the release store and the host lock — and the second one to converge would quietly undo the first.

So ownership is written down. ob bootstrap records the owning application and the environment that claimed the host, and every mutating command reads that record before it does anything. A different application is refused with host_owner_mismatch.

Runtime resources carry the same boundary. Containers, volumes, and networks created by Onebox have an onebox.app label, and preflight includes the application default network (<app>_default) and service network (onebox_services) in its collision set. Both networks are external to a release’s Compose lifecycle: a release can be removed without deleting a network still used by an unmanaged proxy or a supporting service. Networks created by older versions cannot be labelled in place; Onebox accepts one only when its Compose project or durable service state independently proves the same legacy owner. A matching name by itself is never ownership evidence. Full destruction removes both external networks before deleting that evidence and releasing host ownership. If Docker reports a remaining endpoint, destruction stops and tells the operator to detach it, leaving Onebox’s state available for a safe retry.

A different environment of the same application is refused too, with host_environment_mismatch, and that refusal is the less obvious one. Every runtime name Onebox derives — the Compose project, container names, volume names — carries the application and not the environment, because one box runs one application. Point staging at production’s server and nothing collides: staging finds names that look like its own and adopts production’s containers and volumes. The owner record is the only place that difference is visible, which is why it records the environment.

A host claimed before environments were recorded carries the application alone. That still identifies the owner, so it is honoured rather than refused; the next ob bootstrap completes the record.

ob bootstrap is the one mutation that may claim an unowned host — that is what it is for. Every other mutation requires the record to exist and to name this application, so a mistyped target cannot silently adopt a machine by deploying to it. ob preflight reads without claiming, and reports an unowned host as ready rather than as a failure.

The check fails closed in both directions. An owner file that cannot be read is not treated as an absent one — “unowned” and “unreadable” would otherwise be the same answer, and the wrong one is destructive.

Full destruction releases the record, which is what makes a host reusable for a different application rather than permanently claimed. Partial destruction does not: while volumes or service credentials remain, the record stays, because every command is gated on it and releasing early would strand the owner outside their own data.

If Onebox owns a supporting database, it must say exactly when that ownership includes recovery. A boundary that only ever expands is a marketing claim, not an engineering one.

PostgreSQL has an executable backup contract, but declaring it is only a request. Protection begins after ob backup enable has installed the runtime, enabled continuous archiving, and taken the first base backup. Workload volumes are not covered, and every other durable driver refuses a backup policy rather than accepting one it cannot honour. ob doctor reports every durable surface that still has no off-host copy, because silence there would read as approval.

Onebox owns the box, not the world:

  • A customer with root can still bypass it.
  • A compromised host can lie about its own evidence.
  • Onebox does not claim universal reversibility, high availability, or protection from an adversarial infrastructure provider.

Those are limits of the arrangement, not gaps in it.

Breadth and honesty are reconciled by making the guarantee level part of the contract and visible wherever the service appears. A service is either user-authored or Onebox-managed, never both.

Every durable driver runs pinned, persisted, health-gated, capped, observed, and outside application rollback. PostgreSQL alone can add the executable recovery contract today: continuous archiving, point-in-time restore, repository status, and an on-demand recovery drill. Other drivers remain explicit about the missing contract and refuse backup at validation.

The ordering is the point: a declaration is never presented as proof that protection is running. See Evidence, not declaration.