Concepts
Workspaces, teams, apps, deployments, channels, grants — the model behind VibeHost.
VibeHost has a small number of moving parts. Internalize this page and the CLI / dashboard / API surface will feel obvious.
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 this diagram does not say but you'll feel anyway:
- Apps are owned by the workspace, not by a team. A team's relationship to an app is granted, not owned.
- Teams are how you group humans, not how you group apps. They exist so you can grant access to many apps with one grant.
- A user can belong to multiple workspaces and multiple teams within each.
- Rolling back doesn't delete anything. The previous deployment keeps its
immutableUrlforever — rollback just moves the channel alias.
Workspaces
The billing and admin boundary. A workspace owns one plan, one bill, one set of platform-admin overrides. It also 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 — switch with vibehost whoami to inspect, or pass --workspace to any command.
Teams
A way to group 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 1–3 person workspaces, 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
One project, one deploy target. Created with the following — the default runtime is static:
vibehost app create my-siteApp name rules: [a-z][a-z0-9-]*, 2–40 chars, unique within workspace.
The name is the app's immutable unique slug — it appears in URLs (my-site-acme.vibehost.space) and never changes after creation. For humans, an app also carries two optional display-only fields:
displayName(1–100 chars) — what the dashboard shows; falls back to the slug when unset.description(1–500 chars) — a short blurb so you can tell apps apart at a glance.
Both are mutable and non-unique — change them 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"Optionally tag an app with a primary team — surfaces in the dashboard as "owned by the Web team", but doesn't gate access by itself. Access is gated by grants (next section).
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, immutableUrl, 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
What "client build" really means: the build step (npm run build) runs on your machine. The server validates, dedups, and assembles — no npm install or framework build inside our API. This is the only way the platform scales.
Channels
Preview deploys are channels, not git branches. The server has zero git knowledge.
vibehost deploy --channel production
vibehost deploy --channel pr-42
vibehost deploy --channel dark-mode-experimentEvery channel gets its own alias URL. Each channel runs independently — a deploy in dark-mode never affects production.
flowchart LR
subgraph Channels
Prod["production<br/>my-site.vibehost.space"]
PR42["pr-42<br/>my-site-pr-42.vibehost.space"]
Dark["dark-mode<br/>my-site-dark-mode.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.
Grants — how access actually works
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.
Two grant types:
- Team grant —
(app, team, role). Every member of the team picks up the role. - Email grant —
(app, email, role). Targets one person; the email need not match an existing user (invite-before-signup works).
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. Password is the only true AND-gate — when set, it applies to every path. Share links bypass visibility/grant, but NOT password — a valid share-link cookie or ?share=<token> URL param satisfies the share-link gate (skipping visibility/grant), but the password gate still applies if it's set. See Grants and visibility for the full ordered walkthrough.
flowchart TD
Req[Visitor request] --> ShareCk{"Valid share-link cookie<br/>or ?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 "make it public" alone doesn't unlock an app that also has a password set — the password gate is its own AND-composed layer that applies to every path, including the share-link path. The share-link bypass on visibility/grant is the explicit exception: minting a share link IS the operator's consent that "anyone with this URL can pass the visibility/grant check", but it never overrides a password.
See Grants and visibility for the full model and decision tree.
Deployments
An immutable artifact + a runtime config. Every deploy gets:
id— opaque deployment IDimmutableUrl— permanent URL that always points to this exact versionurl— alias URL that moves with the live deployment on this channel
Rolling back doesn't mutate or delete the old deployment — it just points the channel alias at the older immutableUrl. The previous deploy keeps its own URL forever (until you app delete or the workspace is pruned).
Old deployments are pruned by vibehost gc (owner/admin only, default keeps last 5 per channel).
Personal access tokens
Long-lived bearer tokens for CI and external integrations. Scoped per resource group, optionally bound to a specific workspace and team, optionally bound to specific app IDs.
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 handles API + dashboard; tenant apps run in isolated edge sandboxes — no shared process. Everything goes through the same REST API; CLI, MCP, and dashboard all call the same endpoints with the same auth checks.
Naming cheat sheet
| Term | What it means | Common confusion |
|---|---|---|
| Workspace | The billing boundary; owns apps + teams | Not "team" — a team is a group of humans inside the workspace |
| Team | A grant target — give one team access to many apps | Doesn't own apps; just receives grants |
| App | One deploy target (one runtime, one set of channels) | Not "project" — sometimes one product is multiple apps |
| Channel | A named slot for a deployment (production, pr-42) | Not a git branch — server has zero git knowledge |
| Deployment | An immutable artifact + its immutableUrl | 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 | public / workspace / private — first access gate | Not an alternative to grants; both apply |
| Share link | A cookie-issuing URL for time-bound public-ish access | Different from "public" — individually revocable, expirable |