Concepts
The model behind VibeHost: workspaces, teams, apps, deployments, channels, and grants.
VibeHost has a small number of moving parts. The CLI, the dashboard, and the API all work on the same few objects, and this page describes them.
The shape of an account
graph TD
W["Workspace<br/>billing + admin boundary"] --> T1[Team A]
W --> T2[Team B]
W --> A1["App: my-site"]
W --> A2["App: my-app"]
A1 --> C1["channel: production"]
A1 --> C2["channel: pr-42"]
C1 --> D1["Deployment depl_abc<br/>healthy, current"]
C1 -.older.-> D2["Deployment depl_xyz<br/>healthy, superseded"]
C2 --> D3["Deployment depl_def<br/>healthy, current"]
T1 -.grant: deployer.-> A1
T2 -.grant: viewer.-> A2
style W fill:#f4d8c5
style A1 fill:#d8e8f5
style A2 fill:#d8e8f5
style C1 fill:#fff4d8
style C2 fill:#fff4d8
A few things the diagram doesn't show:
- The workspace owns every app. A team never owns an app; it only holds a grant on it.
- Teams group people, not apps. They exist so you can grant access to many apps with one grant.
- A user can belong to multiple workspaces, and to multiple teams within each.
- Rolling back doesn't delete anything. A rollback only moves the channel alias, and the previous deployment is kept until
vibehost gcprunes it.
Workspaces
A workspace is the billing and admin boundary. It has one plan, one bill, and one set of platform-admin overrides, and it owns every app, team, deployment, and audit log row inside it.
Workspace member roles:
| Role | What it unlocks |
|---|---|
member | Be in the workspace; see what app/team grants give you |
admin | Create teams, manage workspace settings, see all apps |
owner | Plus billing, plan changes, workspace deletion |
A user can belong to multiple workspaces. Run vibehost whoami to see which one you're in, or pass --workspace to any command.
Teams
A team groups members inside a workspace. Teams exist so you can issue one grant ("the Web team can deploy to all marketing apps") instead of N email grants.
Team member roles:
| Role | What it unlocks |
|---|---|
member | Be in the team; receive whatever grants the team has |
manager | Manage team members + settings |
owner | Plus delete the team |
For a workspace of one to three people, one team is usually enough. Add more when you want grant isolation between sub-groups (e.g. contractors vs full-time, web vs mobile).
Apps
An app is one project with one deploy target. Create one like this (the default runtime is static):
vibehost app create my-siteApp names match [a-z][a-z0-9-]*, are 2 to 40 characters long, and must be unique within the workspace.
The name is the app's unique slug. It appears in URLs (my-site-acme.vibehost.space) and can't be changed after creation. An app also has two optional fields that are only for display:
displayName(1 to 100 characters) is what the dashboard shows. When it's unset, the dashboard shows the slug.description(1 to 500 characters) is a short blurb that helps you tell apps apart.
Neither has to be unique, and you can change either one at any time without touching a URL:
vibehost app create my-site --display-name "Acme Marketing Site" --description "Landing pages for the spring campaign"
vibehost app update my-site --display-name "Acme Site v2"An app belongs to its workspace and to no team. Teams reach an app only through grants, as described below.
Deployment lifecycle
sequenceDiagram
actor You as You / agent
participant CLI
participant API as api.vibehost.com
participant Storage as Asset storage
participant Edge as Edge runtime
You->>CLI: vibehost deploy ./dist
CLI->>CLI: build (already done) + tarball + validate
CLI->>API: POST /deployments (chunked blobs)
API->>API: dedup against existing blobs
API->>Storage: assemble release
API-->>CLI: depl_abc { url, status: pending }
API->>Edge: update channel alias
Edge-->>API: alias active
API-->>CLI: status: healthy
CLI-->>You: print URL
Note over You,Edge: From tarball POST to healthy URL: ~3–8s
In a client build, the build step (npm run build) runs on your machine. The server only validates, deduplicates, and assembles what you upload. It never runs npm install or a framework build inside our API, and keeping builds out of the API is how the platform scales.
Channels
Preview deploys are channels, not git branches. The server knows nothing about git.
vibehost deploy --channel production
vibehost deploy --channel pr-42
vibehost deploy --channel dark-mode-experimentEvery channel gets its own alias URL and runs independently. A deploy in dark-mode never affects production.
flowchart LR
subgraph Channels
Prod["production<br/>my-site-acme.vibehost.space"]
PR42["pr-42<br/>my-site-ch-pr-42-acme.vibehost.space"]
Dark["dark-mode<br/>my-site-ch-dark-mode-acme.vibehost.space"]
end
Prod -.alias.-> A[depl_001]
PR42 -.alias.-> B[depl_042]
Dark -.alias.-> C[depl_dark]
D[depl_old] -.superseded.-> Prod
Promote between channels without re-uploading:
vibehost promote depl_042 --to-channel production --app my-siteThe artifact at depl_042 doesn't re-upload. Only the production alias moves to point at it.
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-site. 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.
How grants decide access
To do anything on an app, a user needs an app-level grant. Grants are additive: if both your team and your email have grants, the effective role is the max.
There are two kinds of grant:
- A team grant is
(app, team, role). Every member of the team gets the role. - An email grant is
(app, email, role)and targets one person. The email doesn't have to belong to an existing user, so you can grant access before someone signs up.
App-level roles:
| Role | Can do |
|---|---|
viewer | View deployments, see logs |
deployer | Above + push new deploys, promote, rollback |
admin | Above + manage grants, settings, custom domains |
How a request is gated
Every viewer request runs through the platform's authz check. The password is the only gate that applies to every path, whenever one is set. Share links skip the visibility and grant checks but not the password. A valid share-link cookie or ?__vh_share=<token> URL parameter passes the share-link gate, and the password gate still applies after it. See Grants and visibility for the full ordered walkthrough.
flowchart TD
Req[Visitor request] --> ShareCk{"Valid share-link cookie<br/>or ?__vh_share= token<br/>+ share row still active?"}
ShareCk -- yes --> Pwd{Password gate set?}
ShareCk -- no --> Vis{Visibility?}
Vis -- public --> Pwd
Vis -- workspace --> WMem{Is workspace member?}
Vis -- private --> Grant{Has grant?}
WMem -- yes --> Pwd
WMem -- no --> Deny401["401 challenge"]
Grant -- yes --> Pwd
Grant -- no --> Deny401
Pwd -- no --> Allow[Serve content]
Pwd -- yes --> PwOk{"Correct password<br/>or cookie?"}
PwOk -- yes --> Allow
PwOk -- no --> Deny401
style Allow fill:#d4f4d4
style Deny401 fill:#f4d4d4
This is why making an app public doesn't unlock it when it also has a password. The password is a separate layer that every path has to pass, including the share-link path. Skipping visibility and grants is the one exception share links get, because minting a share link is the operator saying that anyone with the URL may pass those checks. It never overrides a password.
See Grants and visibility for the full model and decision tree.
Deployments
A deployment is an immutable artifact plus a runtime config. Every deploy gets two identifiers:
id, an opaque deployment IDurl, the alias URL that moves with the live deployment on this channel
The response also has an immutableUrl field, meant for a URL pinned to this exact version. vibehost.com returns null for it on new deployments, and URLs stored on older ones no longer resolve, so use the id when you need to name one version (see the glossary).
Rolling back doesn't change or delete the old deployment. It points the channel alias back at the older deployment. Old deployments are kept until vibehost gc prunes them or you app delete.
vibehost gc prunes old deployments. Only owners and admins can run it, and by default it keeps the last 5 per channel.
Personal access tokens
Personal access tokens are long-lived bearer tokens for CI and external integrations. Each token is scoped per resource group. You can also bind it to a specific workspace and team, to specific app IDs, or to both.
See Personal access tokens for the full scope list and rotation guidance.
How the pieces talk
graph LR
subgraph Clients
You[You / CLI]
Agent[Agent / MCP]
CI[CI / PAT]
DB1[Dashboard]
end
subgraph ControlPlane["Control plane"]
API[api.vibehost.com/api/v1]
end
subgraph EdgeRuntime["Edge runtime"]
Edge[Tenant router]
Static[Static assets]
end
You --> API
Agent --> API
CI --> API
DB1 --> API
API -.assets.-> Static
Static --> Edge
Edge --> Visitor(("*.vibehost.space<br/>visitor"))
The control plane runs the API and the dashboard. Tenant apps run in isolated edge sandboxes and share no process with it. The CLI, MCP, and the dashboard all call the same REST endpoints with the same auth checks.
Naming cheat sheet
| Term | What it means | Common confusion |
|---|---|---|
| Workspace | The billing boundary; owns apps and teams | Not a team, which is a group of people inside the workspace |
| Team | A grant target that gives one group access to many apps | Doesn't own apps, only receives grants |
| App | One deploy target (one runtime, one set of channels) | Not a project. One product is sometimes several apps |
| Channel | A named slot for a deployment (production, pr-42) | Not a git branch. The server knows nothing about git |
| Deployment | An immutable artifact, identified by its deployment ID | Surviving rollback is by design, not a quota leak |
| Grant | Per-app role given to a team or an email | Composes with visibility / password; never short-circuits |
| Visibility | Who gets in without a share link: public, workspace, or private | Not an alternative to grants; both apply |
| Share link | A cookie-issuing URL for time-bound public-ish access | Unlike public, each link can expire and be revoked on its own |
Every other term the docs use is in the Glossary.