VibeHost
Guides

Channels

Preview deploys without branches. Roll back without re-uploading.

VibeHost doesn't know about git. Preview deploys are channels, which are named slots on the same app.

my-app
  ├── channel: production       ──► live deployment id #42
  ├── channel: dark-mode        ──► live deployment id #38
  └── channel: pr-138           ──► live deployment id #41

Each channel has its own alias URL. They run in parallel. A deploy in dark-mode never affects production.

Deploy to a channel

production is the default channel.

vibehost deploy --channel production
vibehost deploy --channel dark-mode
vibehost deploy --channel pr-138

A channel is created on its first deploy. The URLs look like this:

  • https://<app>-<workspace>.vibehost.space, the production channel alias
  • https://<app>-ch-dark-mode-<workspace>.vibehost.space, a preview channel alias

Both are aliases. Each serves whatever deployment is current on its channel. There is no per-deployment URL on vibehost.com yet (the deploy response's immutableUrl is null). When you need a link that stays on one version, for example in a PR review comment, a design handoff, or a demo, deploy that version to a channel of its own and don't deploy to that channel again.

Promote between channels

vibehost promote currently fails for static apps on vibehost.com with an INTERNAL error. Until that's fixed, deploy the same build output to the target channel instead, for example vibehost deploy ./dist --channel production --app my-app. Deploy the directory you tested without rebuilding it, and the files that go live are the files you tested. Uploads are content-addressed, so nothing the server already has is transferred again. vibehost rollback isn't affected.

Once you've validated a preview, promote a specific deployment into another channel without re-uploading. Take the deployment ID from vibehost logs output or from data.deployments[].id in the API, then run:

vibehost promote <deploymentId> --to-channel production --app my-app

The artifact goes live in the target channel with the same bytes. Only the channel alias is new.

The bytes you reviewed are the bytes that go live. There is no rebuild in between that could introduce a regression.

Rollback

Roll back production to the previous deploy, or target a specific channel:

vibehost rollback --app my-app
vibehost rollback --app my-app --channel pr-138

Rolling back doesn't delete anything. It points the channel alias back at an older deployment. The current deployment is kept too.

Common patterns

Use the PR number as the channel name:

# .github/workflows/preview.yml
- run: vibehost deploy --channel pr-${{ github.event.pull_request.number }}

Authenticate CI with a personal access token scoped to apps:deploy for this app only.

First, CI builds every main commit and deploys the output to the staging channel:

vibehost deploy ./dist --channel staging --app my-app

Keep ./dist between the two steps. If they run as separate CI jobs, upload it as a build artifact and download it in the production job. Then, after QA approves, deploy the same ./dist to production without rebuilding it:

vibehost deploy ./dist --channel production --app my-app

vibehost promote would do this step by deployment ID, but it currently fails for static apps (see Promote between channels).

vibehost deploy --channel variant-a
vibehost deploy --channel variant-b

Send traffic to each channel through its own custom domain or share links.

List and clean up

Delete a channel after its PR merges:

vibehost channel list --app my-app
vibehost channel delete pr-138 --app my-app

A channel costs nothing beyond the deployment it currently points to. Delete it when the PR closes to keep your dashboard tidy.

Channel naming rules

Channel names live in URLs (<app>-ch-<channel>-<workspace>.vibehost.space), so they have to be DNS-safe.

  • Lowercase, [a-z][a-z0-9-]*, 1 to 32 characters.
  • production is the default channel. A deploy without --channel creates it.
  • pr-NN, variant-N, dark-mode, feature-foo all work fine.
  • Avoid underscores (_). They're not valid in DNS labels.
  • Avoid leading digits. DNS allows them, but some legacy resolvers don't.

You can't rename a channel, because the URL would break. Create a new channel, deploy the same build to it, and delete the old one.

Supersede behavior

When you deploy to a channel that already has a deployment, the previous deployment is superseded but not deleted. The channel alias moves to the new deployment, and the old one stays in the app's deployment list.

Before              After deploy
─────────           ────────────────
production          production
  └─ depl_abc         └─ depl_xyz   (current, healthy)
                         depl_abc   (superseded, kept)

Supersede is per channel. A new deploy to dark-mode does not supersede the current deploy on production. Even if both channels happen to serve the same build, the channels are independent slots.

vibehost gc is the only way old superseded deployments get cleaned up. It keeps the most recent N per channel (default 5), and only an owner or admin can run it.

Three end-to-end workflows

GitHub Actions config (PR opened / synchronized):

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

jobs:
  preview:
    if: github.event.action != 'closed'
    runs-on: ubuntu-latest
    env:
      VIBEHOST_TOKEN: ${{ secrets.VIBEHOST_TOKEN }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with: { node-version: 24 }
      - run: npm ci && npm run build
      - run: curl -fsSL -o vibehost-install.sh https://vibehost.com/install.sh && sh vibehost-install.sh && rm vibehost-install.sh
      - id: deploy
        run: |
          URL=$(~/.vibehost/cli/vibehost deploy ./dist \
            --app my-site --channel pr-${{ github.event.pull_request.number }} \
            --json | jq -r '.data.url')
          echo "url=$URL" >> $GITHUB_OUTPUT
      - uses: thollander/actions-comment-pull-request@v3
        with:
          message: "🚀 Preview: ${{ steps.deploy.outputs.url }}"

  cleanup:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    env:
      VIBEHOST_TOKEN: ${{ secrets.VIBEHOST_TOKEN }}
    steps:
      - run: curl -fsSL -o vibehost-install.sh https://vibehost.com/install.sh && sh vibehost-install.sh && rm vibehost-install.sh
      - run: |
          ~/.vibehost/cli/vibehost channel delete pr-${{ github.event.pull_request.number }} \
            --app my-site --force

The reviewer opens the bot's preview link, approves, and merges. The closed job then deletes the preview channel.

Every main commit deploys to staging and soaks there for a while or until it passes some checks. Then a person or CI ships the same build to production.

In CI, on every main commit, build and deploy to the staging channel. Keep ./dist (for example as a CI artifact) so the production step can reuse it:

vibehost deploy ./dist --app my-app --channel staging

Let it soak. Run integration tests, wait for the healthcheck, and so on.

./scripts/soak-tests.sh "https://my-app-ch-staging-acme.vibehost.space" || exit 1

Then deploy the same ./dist to production. The bytes you tested are the bytes that go live.

vibehost deploy ./dist --app my-app --channel production

There's no rebuild between staging and production, and the upload skips every file the server already has. vibehost promote <deploymentId> --to-channel production would do the same by deployment ID, but it currently fails for static apps (see Promote between channels).

Branch the same product into two visual variants for design feedback:

git checkout design-variant-a && npm run build && mv dist dist-a
vibehost deploy ./dist-a --app marketing --channel variant-a

git checkout design-variant-b && npm run build && mv dist dist-b
vibehost deploy ./dist-b --app marketing --channel variant-b

Keep dist-a and dist-b (for example as CI artifacts) until the team decides.

Share both URLs in your design review tool. Each channel has its own alias and its own deployment, so shipping variant-b to production doesn't touch variant-a.

When the team picks variant B, deploy the dist-b they reviewed. Don't check out and rebuild, so the bytes that go live are the bytes the team saw:

vibehost deploy ./dist-b --app marketing --channel production
vibehost channel delete variant-a --app marketing --force
vibehost channel delete variant-b --app marketing --force

Channels and storage

Each channel keeps its own deployments, and their bytes count toward the workspace storage cap. To reclaim space, run vibehost gc. It prunes old deployments and keeps the last 5 per channel by default.

What channels are not

  • Not git branches. VibeHost knows nothing about git. The CLI names the channel, and the server stores the name.
  • Not environments. "Staging" and "production" are only channel names. There's no environment-scoped config, because env vars are per app, not per channel. Use separate apps if you need different env vars per stage.
  • Not access scopes. Channels don't gate access. Visibility, passwords, and share links do (see Grants and visibility).
  • Not free of conflict. Two parallel deploys to the same channel land in order, and the last one gets the alias. Don't run several CI jobs that deploy to the same channel at once.

On this page