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 #41Each 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-138Channels auto-create on first deploy. URLs:
https://<app>.vibehost.space— production channel aliashttps://<app>-dark-mode.vibehost.space— preview channel aliashttps://<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-appThe 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-138Rolling 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 --jsonThen, after QA approves, promote without rebuild:
vibehost promote <deploymentId> --to-channel production --app my-appvibehost deploy --channel variant-a
vibehost deploy --channel variant-bSend 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-appChannels 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. productionis the default channel — created on first deploy if no--channelflag.pr-NN,variant-N,dark-mode,feature-fooall 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 --forceReviewer 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-idSoak — run integration tests, wait for the healthcheck, etc.:
./scripts/soak-tests.sh "https://my-app-staging.vibehost.space" || exit 1Promote — the bytes you tested are the bytes that go live:
vibehost promote "$DEPLOY" --to-channel production --app my-appThis 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-bShare 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 --forceChannels per app — limits
| Plan | Max channels per app |
|---|---|
| Free | 50 (incl. production) |
| Business | 200 |
| Enterprise | Negotiable |
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.