
Cargo Hosting
- 3.1k installs
- 15 repo stars
- Updated August 3, 2026
- getcargohq/cargo-skills
Build, deploy, promote, and manage Cargo Hosting apps and edge workers (Vite SPAs on *.cargo.app and serverless HTTP handlers) via the CLI.
About
Builds and deploys hosted apps and serverless edge workers on Cargo Hosting, including the deployments that ship and promote them. A developer uses it when scaffolding, deploying, or promoting a hosted app or worker.
- Deploy Vite SPAs and edge HTTP handlers
- Manage and promote deployments
Cargo Hosting by the numbers
- 3,106 all-time installs (skills.sh)
- +559 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #50 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/getcargohq/cargo-skills --skill cargo-hostingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3.1k |
|---|---|
| repo stars | ★ 15 |
| Last updated | August 3, 2026 |
| Repository | getcargohq/cargo-skills ↗ |
What it does
Build, deploy, promote, and manage Cargo Hosting apps and edge workers (Vite SPAs on *.cargo.app and serverless HTTP handlers) via the CLI.
Files
Cargo CLI — Hosting
Cargo Hosting runs two kinds of workspace-scoped resources, plus the deployments that ship them:
- App — a Vite single-page app served on
https://<slug>.cargo.app, built on@cargo-ai/app-sdk(Vite + refine + shadcn primitives, withgetCargoEnv()/useCargoApi()wired to the workspace). - Worker — a serverless HTTP handler that runs on the edge (
fetch(request, env)), built on@cargo-ai/worker-sdk(auto OpenAPI 3.1 spec at/openapi.json, Swagger UI at/docs). - Deployment — one build+upload of a local source directory to an app or worker. A deployment is not live until it's promoted.
For organizing apps/workers into folders, use `cargo-workspace-management` (folder …). The--folder-uuidflags here consume those folder UUIDs.
Seereferences/examples/apps.md,references/examples/workers.md, andreferences/examples/deployments.mdfor end-to-end walkthroughs.
See references/response-shapes.md for JSON response structures.See references/troubleshooting.md for common errors and how to fix them.Prerequisites
See `../cargo/references/prerequisites.md` for install, login (--oauth / --token), JSON output conventions, and error shapes. Verify the session with cargo-ai whoami before running any command below.
The lifecycle
Apps and workers follow the same shape — scaffold → create slot → deploy → promote:
init (local scaffold) → create (slot + slug) → deployment create (build+upload) → deployment promote (go live)1. Scaffold a local project from a template — hosting app init <dir> / hosting worker init <dir>. 2. Create the slot in the workspace — hosting app create --name --slug → appUuid (or workerUuid). The --slug becomes the subdomain and must be globally unique within the hosting domain. 3. (apps, optional) Wire local dev — hosting app env <appUuid> prints the .env.local lines a local copy needs (Cargo OAuth + workspace + app UUID + API URL). 4. Deploy — hosting deployment create --app-uuid <uuid> --source <dir> uploads the source; the backend runs npm ci && vite build (apps) or bundles the entrypoint (workers) in a sandbox. Returns a deploymentUuid. 5. Promote — hosting deployment promote --uuid <deploymentUuid> points the live URL at that build.
Deploys build asynchronously — poll `hosting deployment get <uuid>` until the status is terminal before promoting (see Async polling).
Apps
# Discover
cargo-ai hosting app list # all apps (filter with --folder-uuid <uuid>)
cargo-ai hosting app get <uuid> # one app's details + URL
# Scaffold locally (Vite + @cargo-ai/app-sdk)
cargo-ai hosting app init ./my-app --list-templates # see available templates, then:
cargo-ai hosting app init ./my-app --template blank --name "My App"
# Create the slot (slug must be globally unique → it's the subdomain)
cargo-ai hosting app create --name "My App" --slug my-app --folder-uuid <folder-uuid>
# Print .env.local for local development
cargo-ai hosting app env <app-uuid>
cargo-ai hosting app env <app-uuid> --api-url https://api.getcargo.io
# Update / remove
cargo-ai hosting app update --uuid <app-uuid> --name "Renamed"
cargo-ai hosting app update --uuid <app-uuid> --folder-uuid null # move to workspace root
cargo-ai hosting app remove <app-uuid> # also removes its deploymentsTemplates: blank (minimal starting point) and territories-overview (read-only territories grid demoing useCargoApi() + react-query). Run app init <dir> --list-templates for the current list.
Workers
Same command shape as apps — substitute worker for app:
cargo-ai hosting worker list # filter with --folder-uuid <uuid>
cargo-ai hosting worker get <uuid>
# Scaffold (edge fetch(request, env) handler on @cargo-ai/worker-sdk)
cargo-ai hosting worker init ./my-worker --list-templates
cargo-ai hosting worker init ./my-worker --template blank --name "My Worker"
cargo-ai hosting worker create --name "My Worker" --slug my-worker --folder-uuid <folder-uuid>
cargo-ai hosting worker update --uuid <worker-uuid> --name "Renamed"
cargo-ai hosting worker remove <worker-uuid> # also removes its deploymentsTemplates: blank (auto OpenAPI spec + Swagger UI) and custom-integration (a Cargo Custom Integration — manifest / actions / extractors / autocompletes / dynamic schemas). Workers have no `env` subcommand — they read config from the env argument passed to fetch at runtime.
Deployments
A deployment belongs to exactly one app or one worker (--app-uuid and --worker-uuid are mutually exclusive).
# List / inspect
cargo-ai hosting deployment list --app-uuid <uuid> # or --worker-uuid <uuid>
cargo-ai hosting deployment get <deployment-uuid> # status + metadata
cargo-ai hosting deployment get-promoted --app-uuid <uuid> # what's currently live
# Build & upload a local source directory (point at the package root, NOT dist/)
cargo-ai hosting deployment create --app-uuid <uuid> --source ./my-app
cargo-ai hosting deployment create --worker-uuid <uuid> --source ./my-worker
# default ignores: node_modules,dist,build,.git,.next — override with --ignore "a,b,c"
# Go live
cargo-ai hosting deployment promote --uuid <deployment-uuid>Critical rules
- `--slug` must be globally unique within the hosting domain — it's the live subdomain (
<slug>.cargo.app). A clash fails atcreate. - Deploying ≠ going live.
deployment createbuilds and uploads; the URL only changes when youdeployment promotethat deployment. Usedeployment get-promotedto see what's live now. - `--source` is the package root, not `dist/`. The build runs in a Cargo sandbox:
npm ci && vite buildfor apps, entrypoint bundling for workers. Shipping a pre-builtdist/will not work. - Builds are async — poll
deployment getuntil terminal before promoting (see below). - `--app-uuid` / `--worker-uuid` are mutually exclusive on
deployment create,deployment list, anddeployment get-promoted. Pass exactly one. - `remove` cascades — removing an app or worker also removes all of its deployments.
- `update --folder-uuid null` (literal string
null) moves a resource back to the workspace root. - Hosting consumes credits monthly per resource. Each app/worker carries a
chargedUntilthat an hourly sweep advances a month at a time, so a live app or worker bills hosting credits on an ongoing basis —removeresources you no longer serve. Track consumption via `cargo-billing`.
Async polling
deployment create kicks off a sandboxed build. The deployment's status moves pending → building → success (or error / cancelled). Poll until terminal, then promote the success one:
cargo-ai hosting deployment get <deployment-uuid> # poll ~2–5s until status is terminalTerminal statuses are success, error, and cancelled — only promote a success deployment. On error, read the deployment's errorMessage (and buildLogS3Filename) to diagnose the build. For the general polling pattern (intervals, retries), see `../cargo-orchestration/references/polling.md`.
Help
Every command supports --help:
cargo-ai hosting app create --help
cargo-ai hosting deployment create --helpApp examples
Apps are Vite single-page apps served on https://<slug>.cargo.app, scaffolded from @cargo-ai/app-sdk.
Scaffold → create → deploy → promote (end to end)
# 1. See what templates exist, then scaffold a local project
cargo-ai hosting app init ./territories --list-templates
cargo-ai hosting app init ./territories --template territories-overview --name "Territories"
# 2. Create the workspace slot. --slug is the live subdomain → must be globally unique.
cargo-ai hosting app create --name "Territories" --slug territories
# → { "uuid": "<app-uuid>", "slug": "territories", "url": "https://territories.cargo.app", ... }
# 3. (optional) Develop locally — write the .env.local the app needs, then run Vite
cargo-ai hosting app env <app-uuid> > ./territories/.env.local
cd ./territories && npm install && npm run dev
# 4. Build & upload (source = package root, not dist/). The backend runs `npm ci && vite build`.
cargo-ai hosting deployment create --app-uuid <app-uuid> --source ./territories
# → { "uuid": "<deployment-uuid>", "status": "...", ... }
# 5. Poll until the build is terminal
cargo-ai hosting deployment get <deployment-uuid>
# 6. Promote to make it live at https://territories.cargo.app
cargo-ai hosting deployment promote --uuid <deployment-uuid>
# 7. Confirm what's live
cargo-ai hosting deployment get-promoted --app-uuid <app-uuid>List and inspect
cargo-ai hosting app list # all apps in the workspace
cargo-ai hosting app list --folder-uuid <uuid> # only apps in one folder
cargo-ai hosting app get <app-uuid> # one app's details + live URLLocal development env
app env prints the .env.local lines a local copy of the app needs — Cargo OAuth client, workspace UUID, app UUID, and API URL — so getCargoEnv() / useCargoApi() talk to the right workspace.
# Default API URL (https://api.getcargo.io)
cargo-ai hosting app env <app-uuid> > ./my-app/.env.local
# Point at a different API (e.g. a staging environment)
cargo-ai hosting app env <app-uuid> --api-url https://api.staging.getcargo.io > ./my-app/.env.localRename, move, remove
# Rename
cargo-ai hosting app update --uuid <app-uuid> --name "Renamed App"
# Move into a folder (folders are managed by cargo-workspace-management)
cargo-ai workspaceManagement folder list # find the folder UUID
cargo-ai hosting app update --uuid <app-uuid> --folder-uuid <folder-uuid>
# Move back to the workspace root (literal string "null")
cargo-ai hosting app update --uuid <app-uuid> --folder-uuid null
# Remove (also removes every deployment of this app)
cargo-ai hosting app remove <app-uuid>Ship a new version of an existing app
The app slot and slug stay put; you just create and promote a fresh deployment.
cargo-ai hosting deployment create --app-uuid <app-uuid> --source ./my-app
# poll deployment get <new-deployment-uuid> until terminal
cargo-ai hosting deployment promote --uuid <new-deployment-uuid>Roll back by promoting an earlier deployment — deployment list --app-uuid <uuid> shows the history; deployment promote --uuid <older-uuid> points the live URL back at it.
Deployment examples
A deployment is one build+upload of a local source directory to an app or worker. Two facts drive everything below:
1. A deployment belongs to exactly one app or worker — --app-uuid and --worker-uuid are mutually exclusive. 2. Building is not promoting. deployment create builds; the live URL only moves when you deployment promote.
Create a deployment
# App: backend runs `npm ci && vite build` in a sandbox
cargo-ai hosting deployment create --app-uuid <app-uuid> --source ./my-app
# Worker: backend bundles the entrypoint
cargo-ai hosting deployment create --worker-uuid <worker-uuid> --source ./my-worker--sourceis the package root (wherepackage.jsonlives), not a pre-builtdist/. The build happens server-side.- Default ignore list:
node_modules,dist,build,.git,.next. Override the whole list with--ignore:
cargo-ai hosting deployment create --app-uuid <app-uuid> --source ./my-app \
--ignore "node_modules,dist,build,.git,.next,coverage,.turbo"Poll the build, then promote
# Builds are async — poll until the status field is terminal
cargo-ai hosting deployment get <deployment-uuid>
# when terminal (built/succeeded), promote:
cargo-ai hosting deployment promote --uuid <deployment-uuid>If the build failed, inspect the deployment record for the error and fix the source before re-running deployment create. See ../response-shapes.md for the fields to check.
List deployment history
cargo-ai hosting deployment list --app-uuid <app-uuid> # newest first
cargo-ai hosting deployment list --worker-uuid <worker-uuid>See what's currently live
cargo-ai hosting deployment get-promoted --app-uuid <app-uuid>
cargo-ai hosting deployment get-promoted --worker-uuid <worker-uuid>Roll back to a previous deployment
Promotion just points the live URL at a deployment, so rolling back is promoting an older one — no rebuild needed.
# 1. Find the deployment you want to go back to
cargo-ai hosting deployment list --app-uuid <app-uuid>
# 2. Promote it
cargo-ai hosting deployment promote --uuid <older-deployment-uuid>
# 3. Verify
cargo-ai hosting deployment get-promoted --app-uuid <app-uuid>Worker examples
Workers are serverless HTTP handlers that run on the edge — a standard fetch(request, env) entrypoint built on @cargo-ai/worker-sdk. The blank template ships an automatic OpenAPI 3.1 spec at /openapi.json and Swagger UI at /docs.
Scaffold → create → deploy → promote (end to end)
# 1. Scaffold a local worker project
cargo-ai hosting worker init ./my-api --list-templates
cargo-ai hosting worker init ./my-api --template blank --name "My API"
# 2. Create the workspace slot. --slug is the live subdomain → must be globally unique.
cargo-ai hosting worker create --name "My API" --slug my-api
# → { "uuid": "<worker-uuid>", "slug": "my-api", "url": "https://my-api.cargo.app", ... }
# 3. Build & upload (source = package root). The backend bundles the entrypoint.
cargo-ai hosting deployment create --worker-uuid <worker-uuid> --source ./my-api
# → { "uuid": "<deployment-uuid>", "status": "...", ... }
# 4. Poll until the build is terminal
cargo-ai hosting deployment get <deployment-uuid>
# 5. Promote to go live
cargo-ai hosting deployment promote --uuid <deployment-uuid>
# 6. Confirm what's live, then hit it
cargo-ai hosting deployment get-promoted --worker-uuid <worker-uuid>
curl https://my-api.cargo.app/openapi.jsonList and inspect
cargo-ai hosting worker list # all workers
cargo-ai hosting worker list --folder-uuid <uuid> # only workers in one folder
cargo-ai hosting worker get <worker-uuid> # one worker's details + URLTemplates
cargo-ai hosting worker init ./tmp --list-templates- `blank` — edge worker on
@cargo-ai/worker-sdkwith automatic OpenAPI 3.1 spec at/openapi.jsonand Swagger UI at/docs. - `custom-integration` — a Cargo Custom Integration worker: manifest / actions / extractors / autocompletes / dynamic schemas, also with
/openapi.json. Use this when you're building an integration the rest of Cargo can call as a connector action.
Rename, move, remove
cargo-ai hosting worker update --uuid <worker-uuid> --name "Renamed Worker"
cargo-ai hosting worker update --uuid <worker-uuid> --folder-uuid <folder-uuid>
cargo-ai hosting worker update --uuid <worker-uuid> --folder-uuid null # back to root
cargo-ai hosting worker remove <worker-uuid> # also removes its deploymentsApp vs worker — when to use which
- App — you want a UI on
*.cargo.app(dashboard, internal tool, data grid). Vite SPA,app init, has anenvsubcommand for local dev. - Worker — you want an HTTP endpoint with no UI (webhook receiver, API, custom integration backend). Edge
fetchhandler,worker init, noenvsubcommand — runtime config arrives via theenvargument tofetch.
Hosting response shapes
JSON response structures for the hosting domain. All commands output JSON to stdout; failures exit non-zero with {"errorMessage": "..."}.
App (hosting app get / items in hosting app list)
{
"uuid": "app-uuid",
"workspaceUuid": "...",
"name": "My App",
"description": null,
"slug": "my-app",
"url": "https://my-app.cargo.app",
"userUuid": "...",
"folderUuid": null,
"promotedDeployment": null,
"chargedUntil": "2026-02-01T00:00:00Z",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-15T00:00:00Z",
"deletedAt": null
}Key fields: uuid (pass as --app-uuid to deployment commands), slug (the live subdomain), url (the live address), folderUuid (null unless filed into a folder), promotedDeployment (the App Deployment object currently live, or null if nothing is promoted yet), chargedUntil (end of the period already billed hosting credits — advanced a month at a time, so hosting an app costs credits monthly; see `cargo-billing`).
Worker (hosting worker get / items in hosting worker list)
Identical to an app, with one difference: promotedDeployment is a Worker Deployment (carries workerUuid + meta, see below). The uuid is passed as --worker-uuid to deployment commands.
Deployment (hosting deployment get / items in hosting deployment list)
A deployment is a discriminated union on kind ("app" | "worker"). Shared fields:
{
"uuid": "deployment-uuid",
"kind": "app",
"appUuid": "app-uuid",
"workspaceUuid": "...",
"status": "success",
"url": "https://my-app.cargo.app",
"sourceS3Path": "...",
"bundleS3Path": "...",
"buildLogS3Filename": "...",
"errorMessage": null,
"meta": {},
"userUuid": "...",
"promotedAt": "2026-01-01T00:01:30Z",
"promotedByUserUuid": "...",
"finishedAt": "2026-01-01T00:01:10Z",
"temporalWorkflowId": "...",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:01:30Z"
}- `kind: "app"` carries
appUuidand an emptymeta({}). - `kind: "worker"` carries
workerUuidinstead ofappUuid, andmeta: { "bundleSha256": "...", "outboundAllowlist": ["..."] }.
Key fields:
uuid— pass todeployment promote --uuid.appUuid/workerUuid— exactly one is set, matchingkind.- `status` — one of
"pending","building","success","error","cancelled". Terminal atsuccess/error/cancelled; only asuccessdeployment is worth promoting. errorMessage— populated whenstatusiserror;buildLogS3Filenamepoints at the build log for diagnosing a failed build.promotedAt/promotedByUserUuid— non-null once this deployment has been promoted to the live URL (this is how "is it live?" is represented — there is no separateisPromotedflag).finishedAt— when the build reached a terminal state.
get-promoted (hosting deployment get-promoted)
Returns the currently-promoted Deployment for the given --app-uuid / --worker-uuid (same shape as above, with promotedAt set), or null/empty if nothing is promoted yet. Equivalent to reading promotedDeployment off the app/worker.
env (hosting app env)
Not JSON — hosting app env <appUuid> prints .env.local lines (Cargo OAuth client, workspace UUID, app UUID, VITE_CARGO_DEPLOYMENT_UUID, API URL) to stdout. Redirect into a file: cargo-ai hosting app env <app-uuid> > .env.local.
init templates (hosting app init <dir> --list-templates)
[
{ "slug": "blank", "description": "..." },
{ "slug": "territories-overview", "description": "..." }
]Workers list their own templates (blank, custom-integration) via hosting worker init <dir> --list-templates. Note --list-templates still requires the <directory> positional argument.
Hosting troubleshooting
Common errors in the hosting domain and how to fix them.
unknown command 'hosting'
The hosting domain shipped in a recent CLI. If cargo-ai hosting --help errors, bump the CLI: npm install -g @cargo-ai/cli@latest.
Slug already taken / create fails on --slug
The --slug is the live subdomain (<slug>.cargo.app) and must be globally unique within the hosting domain — not just unique to your workspace. Pick a more specific slug and re-run create.
I deployed but the URL still shows the old version
deployment create only builds and uploads — it does not change the live URL. Promote the new deployment:
cargo-ai hosting deployment get <deployment-uuid> # confirm the build is terminal/succeeded
cargo-ai hosting deployment promote --uuid <deployment-uuid>
cargo-ai hosting deployment get-promoted --app-uuid <app-uuid> # verify what's livedeployment create build fails
The build runs server-side in a sandbox (npm ci && vite build for apps, entrypoint bundling for workers). A failed build usually means:
- `--source` points at the wrong directory. Pass the package root (where
package.jsonlives), not a pre-builtdist/. - `npm ci` can't resolve the lockfile. Ensure
package-lock.jsonis present and in sync withpackage.json, and that it isn't in the ignore list. - Something needed got ignored. The default ignore list is
node_modules,dist,build,.git,.next. If you override--ignore, you replace the whole list — don't accidentally dropnode_modulesfrom the ignores (it should stay ignored; the sandbox installs deps itself) while keeping source files you need.
When status is error, deployment get <uuid> exposes the cause: read errorMessage, and buildLogS3Filename points at the full build log. Fix the source and re-run deployment create.
--app-uuid and --worker-uuid both passed (or neither)
On deployment create, deployment list, and deployment get-promoted the two flags are mutually exclusive — pass exactly one. A deployment targets one app or one worker, never both.
folderNotFound on --folder-uuid
The folder UUID doesn't exist. Folders are managed by the `cargo-workspace-management` skill — run cargo-ai workspaceManagement folder list to find valid UUIDs. To move a resource back to the workspace root, pass the literal string null: --folder-uuid null.
app env writes the wrong API URL
By default hosting app env points at https://api.getcargo.io. For a different environment, override it: cargo-ai hosting app env <app-uuid> --api-url <url>. Workers have no env subcommand — they receive config via the env argument to fetch(request, env) at runtime.
Removing an app/worker took its deployments too
That's by design — app remove / worker remove cascade to every deployment of that resource. There's no undo; recreate the slot and redeploy if needed.
Still stuck
File a report so the Cargo team can improve the CLI and these docs:
cargo-ai workspaceManagement report create \
--title "<one-line summary>" \
--description "<exact command(s), errorMessage, expected vs actual, UUIDs involved>"