> ## Documentation Index
> Fetch the complete documentation index at: https://docs.boxd.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy on push

> Push to main. See it live on your boxd VM in seconds.

Merge a PR. Within seconds your boxd VM is running the new code on its public `*.boxd.sh` URL. No SSH, no manual `git pull`, no "did it deploy?" Slack messages.

Deploy-on-push is the **sixth hook on the same webhook listener** that powers [`/boxd-preview`](/preview-environments/per-pr-preview-urls) — one listener, one secret, so deploy, previews, and the fix loop all coexist on one golden. After setup, every push to your default branch re-syncs the golden and redeploys it: typically \~7s end-to-end for source-only changes, longer when dependencies or schema need to rebuild.

## How it works

GitHub delivers a `push` webhook to the golden's listener. No GitHub Actions runner, no `BOXD_API_KEY` round-trip, no minutes consumed.

```
push to <default-branch> ──► https://hooks.<your-golden>.boxd.sh/hooks/deploy
                                          │
                                          ▼
                            webhook(8) on the golden VM
                                          │
                                          ▼
                    golden-sync.sh:  git fetch + reset → deploy.sh
                                          │
              ┌───────────────┬───────────┴────────────┐
              ▼               ▼                         ▼
           reload          rebuild                  recreate
        (source-only)   (deps/schema)            (compose/.env)
```

The listener verifies an HMAC signature on every request, then hands off to a fire-and-forget background process that fetches and resets the golden's checkout to the default branch and runs `deploy.sh`. `deploy.sh` is the **same decision engine the preview forks use**: it diffs what changed against what the golden last deployed and picks the cheapest correct action — **hot-reload** for source-only edits (no rebuild), **rebuild** when a dependency/schema path changed, **recreate** when compose/env changed. Because it keeps the golden current *and* warm, the `/boxd-preview` fast path (an issue preview that forks the golden as-is) stays hot.

The VM is long-running. Auto-suspend handles idle cost.

## Setup

Deploy is the last layer in the chain: [**`/boxd-setup-golden`**](/preview-environments/set-up-a-golden) → [**`/boxd-setup-preview`**](/preview-environments/per-pr-preview-urls) → **`/boxd-setup-deploy`**. With those two done, wire deploy on top — **two ways**:

<Tabs>
  <Tab title="From your laptop">
    With the boxd CLI installed, run the skill in the repo:

    ```text theme={"theme":"github-dark"}
    /boxd-setup-deploy
    ```
  </Tab>

  <Tab title="Inside the golden (in-VM)">
    Open an agent shell on the golden (`boxd connect <golden>`) and run it there —
    it wires the VM you're on directly, no `boxd machine exec`/API key:

    ```text theme={"theme":"github-dark"}
    /boxd-setup-deploy
    ```
  </Tab>
</Tabs>

<Note color="#E05A6D">
  Claude Code only for now — [reach out](mailto:contact@boxd.sh) for Codex, OpenCode, or any other agent.
</Note>

The `boxd-setup-deploy` skill just makes the `deploy` hook live and registers **one** `push` webhook on your repo, reusing the listener and HMAC secret `/boxd-setup-preview` already set up. It installs no second service, picks no new port, and commits nothing to your repo.

What you need first:

* A golden VM with **`boxd-setup-preview` already run** (the listener + secret must exist; use `/boxd-setup-golden` then `/boxd-setup-preview` if not).
* `gh` authenticated for the repo with `admin`/`maintain` (to register the webhook) — `enable-deploy` reuses the gh auth preview already persisted, so this usually needs nothing extra.

No `BOXD_API_KEY` repo secret is needed — the listener runs inside your own VM and uses the VM's own identity.

## What you control

The deploy commands are the **same ones you gave `/boxd-setup-preview`** (they live in `/etc/boxd-platform.conf` on the golden) — there's no separate deploy config:

| Tier         | Runs when                                                   | Typical command                            |
| ------------ | ----------------------------------------------------------- | ------------------------------------------ |
| **reload**   | source-only push                                            | usually empty — the dev server's own HMR   |
| **rebuild**  | a rebuild-trigger path changed (lockfiles, schema, codegen) | `npm ci`, `cargo build`, `pnpm install`, … |
| **recreate** | `compose*.yml` / `.env*` changed                            | `docker compose up -d --force-recreate`    |

## Patterns

### Branch previews + main deploy

Combine this with [per-PR preview URLs](/preview-environments/per-pr-preview-urls) — that's the design. Previews fork the golden; deploy keeps the golden current. Set up `/boxd-setup-preview` once, then add `/boxd-setup-deploy` (and `/boxd-setup-fix` for the agent loop) on the same listener.

### Rolling back

The fastest rollback is `git reset --hard <last-good-sha>` followed by your reload/rebuild command, from inside the VM via `boxd machine exec`. Or fork the golden before a risky deploy and keep the fork as a hot standby.

## FAQ

<AccordionGroup>
  <Accordion title="What if my VM is suspended when the deploy webhook fires?">
    The first request to the listener resumes the VM (sub-ms), then the deploy runs normally. Auto-suspend won't drop a request mid-deploy — it requires sustained network idle.
  </Accordion>

  <Accordion title="How do I see what's happening during a deploy?">
    Tail the deploy log on the golden: `boxd machine exec <vm> -- 'sudo tail -f /var/log/golden-deploy.log'`. Every `git fetch` + `deploy.sh` run streams there.
  </Accordion>

  <Accordion title="What about a deploy that takes 90 seconds?">
    GitHub's webhook timeout is 10s, so the listener returns 200 immediately (after queuing the work) and the deploy runs in the background. Pushes during a long deploy are queued, not interleaved.
  </Accordion>

  <Accordion title="Can I use this and per-PR previews together?">
    Yes — deploy is literally a hook on the preview listener. One listener, one secret. Set up `/boxd-setup-preview`, then layer `/boxd-setup-deploy` (and `/boxd-setup-fix`) on top.
  </Accordion>

  <Accordion title="Do I need to commit anything to my repo?">
    No. The platform runs entirely on the golden; nothing is added to your repo. The HMAC secret stays on the golden and in GitHub's webhook config.
  </Accordion>
</AccordionGroup>

## Next

<Columns cols={2}>
  <Card title="Per-PR preview URLs" icon="https://mintcdn.com/azin/Ax1V0serIwQf0x_2/images/icons/branching-paths-up.svg?fit=max&auto=format&n=Ax1V0serIwQf0x_2&q=85&s=06ff61ff2b80a7adc15558368629f9bb" href="/preview-environments/per-pr-preview-urls" width="16" height="16" data-path="images/icons/branching-paths-up.svg">
    Comment `/boxd-preview`, get a URL forked from the golden.
  </Card>

  <Card title="Fork from a golden" icon="https://mintcdn.com/azin/Ax1V0serIwQf0x_2/images/icons/copy.svg?fit=max&auto=format&n=Ax1V0serIwQf0x_2&q=85&s=f3623fe516eebf87b33b3a1022852286" href="/cloud-dev-boxes/fork-from-a-golden" width="16" height="16" data-path="images/icons/copy.svg">
    The primitive behind it all.
  </Card>
</Columns>
