kvbox.sh
Use cases

Preview environments

Every pull request gets its own box and its own HTTPS address, removed when the PR closes.

Give every pull request a box running its branch, at an address reviewers can open: https://myapp-pr-42.kvbox.app. Each preview is a full VM, so a branch that changes the database or the system packages gets its own copy of those too.

With kvbox deploy

For a React app, a Next.js app or a Node server, kvbox deploy already does the work: one box per name, built and live at its address. In a GitHub Actions workflow:

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm install -g @kvbox/cli
      - run: echo "$KVBOX_PASSWORD" | kvbox login -e "$KVBOX_EMAIL" --password-stdin
        env:
          KVBOX_EMAIL: ${{ secrets.KVBOX_EMAIL }}
          KVBOX_PASSWORD: ${{ secrets.KVBOX_PASSWORD }}
      - if: github.event.action != 'closed'
        run: npm ci && kvbox deploy --name pr-${{ github.event.number }} --sub myapp-pr-${{ github.event.number }} -y
      - if: github.event.action == 'closed'
        run: kvbox rm pr-${{ github.event.number }} -y

The first push makes the box; each later push redeploys into it, in seconds for a React app, a minute or two for Next.js. Closing the PR removes the box and frees its address. Add --org acme to keep previews in your team's org.

From a snapshot

For anything else, keep a box with the app set up (dependencies installed, database seeded, services running) and snapshot it. Each preview is then a warm clone of it, up in under a second:

kvbox snapshots new app app-ready --replace     # refresh it when main changes

kvbox new --from app-ready pr-42
kvbox exec pr-42 -- 'cd /srv/app && git fetch origin pull/42/head && git checkout FETCH_HEAD && ./restart.sh'
kvbox claim pr-42 myapp-pr-42 --port 3000

kvbox rm pr-42 -y                               # when the PR closes

A clone starts with no HTTPS address, which is why each preview claims its own. --replace swaps the snapshot in only once the new one is complete, so a preview made meanwhile never gets half of one (Snapshots).

Keeping the bill small

Make previews hibernate when nobody looks at them: kvbox deploy boxes do after an hour without traffic, and a clone takes kvbox vm set pr-42 --auto-hibernate 1h. A reviewer opening the link wakes the box in a few seconds.

On this page