VibeHost
Guides

Channels

Preview deploys without branches. Promote and roll back without re-uploading.

VibeHost doesn't know about git. Preview deploys are channels — 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 — channels are isolated by design.

Deploy to a channel

production is the default channel.

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

Channels auto-create on first deploy. URLs:

  • https://<app>.vibehost.space — production channel alias
  • https://<app>-dark-mode.vibehost.space — preview channel alias
  • https://<deploy-id>.vibehost.space — immutable per-deployment URL

The immutable URL always works — even after rollback or promote. Link to it when you need to reference a specific version (PR review comments, design handoff, demo).

Promote between channels

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

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

The artifact is launched into the target channel — same bytes, same immutableUrl, new channel alias.

This is the killer feature: the bytes you reviewed are the bytes that go live. No rebuild step in the middle that could regress.

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 at an older immutableUrl. The current deployment keeps its URL too; you can re-promote at any time.

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 deploys every main commit to the staging channel — grab data.id (the deployment ID) from the output:

vibehost deploy --channel staging --json

Then, after QA approves, promote without rebuild:

vibehost promote <deploymentId> --to-channel production --app my-app
vibehost deploy --channel variant-a
vibehost deploy --channel variant-b

Send traffic via custom domains or share links per channel.

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

Channels are cheap — there's no per-channel runtime cost beyond the deployment they currently point to. Delete them when the PR closes to keep your dashboard tidy.

Channel naming rules

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

  • Lowercase, [a-z][a-z0-9-]*, 1–32 chars.
  • production is the default channel — created on first deploy if no --channel flag.
  • 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 it but some legacy resolvers don't.

Renames aren't supported (the URL would break). Create a new channel, promote, 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; the old deployment is still reachable at its immutableUrl.

Before              After deploy
─────────           ────────────────
production          production
  └─ depl_abc         └─ depl_xyz   (current, healthy)
                         depl_abc   (superseded, healthy, still has immutableUrl)

Supersede is per-channel: a new deploy to dark-mode does not supersede the current deploy on production. Even if both channels happen to point at the same artifact (after a promote), 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). Owner/admin only.

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 https://vibehost.com/install.sh | sh
      - id: deploy
        run: |
          URL=$(~/.vibehost/bin/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 https://vibehost.com/install.sh | sh
      - run: |
          ~/.vibehost/bin/vibehost channel delete pr-${{ github.event.pull_request.number }} \
            --app my-site --force

Reviewer clicks the bot's preview link, approves, merges. The closed handler deletes the preview channel automatically.

The pattern: every main commit deploys to staging, soaks for some time / passes some checks, then a human (or CI) promotes the same artifact to production.

In CI, on every main commit, deploy to the staging channel and record the deployment ID:

DEPLOY=$(vibehost deploy ./dist --app my-app --channel staging --json | jq -r '.data.id')
echo "$DEPLOY" > /tmp/staging-deploy-id

Soak — run integration tests, wait for the healthcheck, etc.:

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

Promote — the bytes you tested are the bytes that go live:

vibehost promote "$DEPLOY" --to-channel production --app my-app

This is the workflow that gives you the "the bytes I tested are the bytes I shipped" guarantee. There's no rebuild step between staging and production — promote is a metadata move, not a rebuild.

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

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

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

Share both URLs in your design review tool. Each channel has its own alias + isolated deployment — promoting variant-b to production doesn't touch variant-a.

When the team picks variant B:

DEPLOY_B=$(vibehost channel list --app marketing --json | jq -r '.data[] | select(.name=="variant-b") | .currentDeploymentId')
vibehost promote "$DEPLOY_B" --to-channel production --app marketing
vibehost channel delete variant-a --app marketing --force
vibehost channel delete variant-b --app marketing --force

Channels per app — limits

PlanMax channels per app
Free50 (incl. production)
Business200
EnterpriseNegotiable

Channel quota is current channels, not lifetime channels — delete unused channels and the slot frees up.

What channels are NOT

  • Not git branches. VibeHost has zero git awareness. The CLI names the channel; the server stores the name.
  • Not environments. "Staging" and "production" are just channel names. There's no environment-scoped config — 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, password, and share links do (see Grants and visibility).
  • Not free of conflict. Two parallel deploys to the same channel land in order; the last one wins on the alias. Avoid running multiple CI jobs that deploy to the same channel simultaneously.

On this page