*.boxd.sh URL that gets posted on the PR. Reviewers click it and land on a full Linux VM, so two reviewers can poke at two different PRs without stepping on each other’s writes. Close the PR and the machine is destroyed.
The whole thing is two small SDK scripts on top of one primitive: creating a machine from a snapshot.
How it works
You maintain a golden image: a snapshot of a machine with your app installed and running, re-saved on every push to main so it always reflects your latest code. That guide is the prerequisite for this page. On every PR, a script creates a machine from the latest snapshot, named for the PR:https://myapp-pr-482.boxd.sh. On new pushes the machine is recreated from the latest snapshot, and because the name is reused, the URL stays stable across the PR’s whole life. On close, one command removes it.
The scripts
Two small scripts cover the full lifecycle. The recommended way to run them is through the TypeScript or Python SDK, which reads aBOXD_API_KEY from the environment and handles authentication for you.
Bring a preview up, or refresh it after a push:
- TypeScript
- Python
preview-up.ts
- TypeScript
- Python
preview-down.ts
npm ci && sudo systemctl restart myapp) for whatever your stack needs. With the GitHub integration connected, the git fetch inside the preview machine works against private repos with nothing extra to configure.
Wire it into CI
You can wire this into GitHub Actions (or any CI) with two small jobs: run the first script onpull_request opened, synchronize, and reopened, and the teardown on closed, passing the PR number and branch as arguments. Store the key as a repo secret first:
gh pr comment, and scope concurrency per PR number so rapid pushes don’t race each other.
You can of course also run the boxd part on a boxd VM, for example on a self-hosted runner or a small webhook listener living there. Inside a machine, new Boxd() authenticates automatically, so the key disappears entirely and the scripts run unchanged.
Patterns
A stable URL per PR
The machine is named for the PR number, so each PR keepshttps://myapp-pr-<n>.boxd.sh across every push. That makes it a steady target for screenshot diffs and Playwright runs, and reviewers keep refreshing the same link.
Preview data without a staging database
Run a local database on the golden machine before you snapshot it. Every preview then inherits a copy of that data, and writes stay inside that one preview. Reset comes free with the next recreate.More than a URL
Each preview is a full machine you own.ssh myapp-pr-482.boxd gets you a shell in the exact environment the reviewer is looking at, and boxd machine exec lets other jobs run tests against it.
FAQ
What does a preview cost while nobody is clicking it?
What does a preview cost while nobody is clicking it?
Very little, on two levels. The 5-minute auto-suspend freezes an idle preview in RAM at near-zero cost, and clicking the link resumes it in sub-millisecond time. The 30-minute auto-hibernate then parks it on disk, where it costs effectively nothing beyond storage, and the next click wakes it in about 85ms. A PR sitting open over the weekend costs effectively nothing.
What happens if 5 PRs get previews at once?
What happens if 5 PRs get previews at once?
You get 5 machines, one per PR. The default cap is 50 machines per organization, extendable on request, and the golden machine counts toward it too.
How fresh is the code under the branch?
How fresh is the code under the branch?
The preview is created from the latest golden snapshot, which your refresh workflow re-saves on every push to main. The PR branch is then checked out on top, so the diff against main is exactly what the rebuild has to cover.
Why recreate instead of reusing the machine on every push?
Why recreate instead of reusing the machine on every push?
Recreating from the snapshot gives every push a clean, known starting state for the price of one boot, and the reused name keeps the URL stable. If your rebuild is expensive and pushes are frequent, reuse the machine and run the checkout step alone.