Skip to content

Your first deploy

This is the whole path, once. Every command below either states that it contacts nothing, or is explicit about what it changes.

You need:

  • Onebox installed on your machine
  • a repository containing the Compose file you already run
  • one Linux server reachable over SSH, with Docker Engine, Compose, and Buildx available to that SSH account
  • the server’s host key already recorded in known_hosts

If the server has no public SSH port, name the bastion it is reached through — see Deploy through a jump host.

The first three steps are local and contact nothing. ob bootstrap is the first command that changes the server. ob preflight then reads the provisioned host without changing it or contacting a registry; the plan after it is also read-only.

  1. Scaffold a project from the Compose file you already have.

    Terminal window
    ob init

    This writes ob.yml and puts the published schema reference on its first line, so your editor offers completion, hover documentation and inline errors that match what ob validate will say.

  2. Check the contract. Nothing is contacted.

    Terminal window
    ob validate

    Every failure carries a typed code, the path that produced it, and where possible the line and the command that resolves it:

    ✗ ob: "replicaz" is not a field of this contract (line 10); did you mean "replicas"?
    at: workloads.web.replicaz
    code: unknown_field
  3. Read back what Onebox understood.

    Terminal window
    ob canonical
    ob preview

    ob canonical marks each value with where it came from: # default for one Onebox chose, # shorthand for a top-level key it moved into a workload, # override for one an environment supplied. ob preview prints the Compose runtime it will generate. Read both before the first deploy — the difference between a value someone chose and one that appeared by itself is what a person checking a production configuration needs to see.

  4. Prepare the host. This contacts and changes the server.

    Terminal window
    ob bootstrap
  5. Check host readiness. This contacts the server but changes nothing.

    Terminal window
    ob preflight

    Among the host, ownership, path, and collision checks, preflight verifies that Docker Buildx can format image-manifest digests. The capability probe reads local help output only, so it does not resolve an image or consume registry quota.

  6. Review production without changing it.

    Terminal window
    ob plan --out ob-plan.json

    The plan is a mode-0600, digest-protected executable envelope containing the typed operation graph and the exact config, Compose, host-state, image, rendered-Compose and payload bindings. It expires after 15 minutes, and any drift or local payload change requires a new plan.

    Each long-running workload has its own action in that graph:

    • retain leaves every healthy replica running when its complete effective runtime is unchanged;
    • rolling or recreate applies the workload’s configured strategy when it changed;
    • an uncertain comparison uses the configured strategy and names the reason, such as a release-staged env file, secret, bind-mounted payload, adopted Compose service, missing legacy revision, replica drift or unhealthy state.

    Retention is automatic and adds no configuration or selection flag. A retained workload receives no pull, signal, stop, rename or Compose command. ob deploy --redeploy remains the explicit exception: on a whole-application no-op it performs the fresh roll shown in the sealed plan.

  7. Record a short-lived local confirmation for that exact plan.

    Terminal window
    ob approve --plan ob-plan.json --out ob-approval.json

    The confirmation is mode-0600 and digest-bound to the plan, server, inputs, risk, operator label, and expiry. A changed or expired plan needs a new confirmation. It is tamper-evident local ceremony, not authenticated identity.

  8. Deploy with both artifacts.

    Terminal window
    ob deploy --plan ob-plan.json --approval ob-approval.json

    A successful deploy ends by naming the release that is serving and the one it superseded:

    Terminal window
    staged r-0042
    rolling shop-web-1 … healthy
    rolling shop-web-2 … healthy
    retain shop-worker · runtime unchanged
    verified checks/url 200 · X-App-Ready: yes
    serving r-0042 · r-0041 superseded

    Read the observed state back once more:

    Terminal window
    ob status

    Status exits non-zero if the host diverges from the active release.

  • Directory/var/lib/onebox/app/
    • Directoryreleases/
      • Directory20260808T101500Z/ the release just deployed
        • …
      • Directory20260807T093000Z/ retained for rollback
        • …
    • current the symlink the runtime follows
    • journal append-only operation record
    • Directoryservices/ supporting services, outside every release
      • …

Container and volume names are derived and stable. Workload containers are shop-web-1 and shop-web-2; the managed database is onebox-postgres and the host proxy is onebox-proxy. Persistent and provider resources include onebox_postgres_data, onebox_services for the service network, and onebox-ingress for the host proxy network. Once a volume exists, its name cannot change without moving data.

Why plan and local confirmation are separate

Section titled “Why plan and local confirmation are separate”

The split creates a deliberate review ceremony and binds the answer to one exact sealed plan. ob approve records a short-lived local confirmation whose digest detects modification; it is not authenticated identity or proof that the actor requesting the change was technically unable to create it. Environments that need independent authorization require a future external provider, not a local checksum artifact.

For automation, plan and deploy accept structured output, and ob approve sits between them:

Terminal window
ob plan --output json --out ob-plan.json
echo y | ob approve --plan ob-plan.json --out ob-approval.json
ob deploy --output ndjson --plan ob-plan.json --approval ob-approval.json

ob approve always prompts and there is no flag to skip it. A routine plan asks yes or no; one that touches data — a migration, a destructive job, or an unknown data effect — asks for the plan’s identity to be typed back: the release ID for a deploy, the job name for a job run. It reads the answer from stdin without checking for a terminal, so a pipeline supplies y, or that identity for the stronger ceremony. Anything else records nothing and exits cancelled.

A JSON deploy buffers ordered operation events and its result into one envelope. NDJSON streams event records and a terminal result or error record. Diagnostics stay on stderr, so the structured stream is never polluted.

Terminal window
ob status # non-zero on divergence
ob resume # finish an interrupted deploy
ob abort # revert an interrupted deploy
ob rollback # activate the previous release

See Roll back a release.