---
title: "Your first deploy"
summary: "The full path — scaffold ob.yml, inspect what Onebox understood, plan, approve, deploy — with the plan and local-confirmation artifacts explained."
description: "From an existing Compose file to a released application, in one pass."
status: shipped
read_when:
  - "Deploying with Onebox for the first time"
  - "Understanding what a plan and an approval actually are"
---

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

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

   ```sh
   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.

   :::caution
   `ob init` is a starting point, not permission to deploy. Review workload
   types, persistence semantics, readiness, job data effects, and the environment
   server before running a plan.
   :::

2. **Check the contract. Nothing is contacted.**

   ```sh
   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.**

   ```sh
   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.

   ```sh
   ob bootstrap
   ```

5. **Review production without changing it.**

   ```sh
   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.

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

   ```sh
   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.

7. **Deploy with both artifacts.**

   ```sh
   ob deploy --plan ob-plan.json --approval ob-approval.json
   ```

## What you now have on the host

- /var/lib/ob/shop/
  - releases/
    - 20260808T101500Z/ the release just deployed
    - 20260807T093000Z/ retained for rollback
  - current the symlink the runtime follows
  - journal append-only operation record
  - services/ supporting services, outside every release

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

## 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:

```sh
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 release ID to be typed back. It reads the
answer from stdin without checking for a terminal, so a pipeline supplies `y`,
or the release ID 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.

## If something goes wrong

```sh
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](/guides/roll-back).