cargo-hosting
Put something on the internet from Cargo — Vite single-page apps served at https://<slug>.cargo.app and serverless edge workers that answer HTTP requests, plus the deployments that build and promote them. Triggers: "build me a dashboard for this", "host this app", "give me a URL to share", "deploy t
By getcargohq · 6,559 installs
npx skills add getcargohq/cargo-skills --skill cargo-hosting
Source repository · Upstream listing
Cargo CLI — Hosting
Cargo Hosting runs two kinds of workspace scoped resources, plus the deployments that ship them:
App — a static front end (a Vite single page app by default; Astro, Gatsby, Create React App, Parcel and Webpack are detected too, and Next.js, SvelteKit or Nuxt work through their own build script, see [App builds]( app builds)) served on its own subdomain (see [URLs]( urls)). The templates are built on @cargo ai/app sdk (Vite + refine + shadcn primitives, with getCargoEnv() / 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 ](../cargo workspace management/SKILL.md) ( folder … ). The folder uuid flags here consume those folder UUIDs.
See references/examples/apps.md , references/examples/workers.md , and references/examples/deployments.md for 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.
Bootstrap
Already signed in ( cargo ai whoami returns a workspace)? Skip to the next section.
Every command prints JSON to stdout; failures exit non zero with {"errorMessage": "..."} . Anything that creates a run or a batch is async — pass wait until finished or poll the matching get . When the full skill bundle is installed, [ ../cargo/references/prerequisites.md ](../cargo/references/prerequisites.md) adds the CLI version pin, token scopes, and the admin only surface.
The lifecycle
Apps and workers follow the same shape — scaffold → create slot → deploy → promote :
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 part of the subdomain and must be unique within the workspace.
3. (optional) Wire local dev — for an app, hosting app env <appUuid prints the .env.local lines a local copy needs (Cargo OAuth + workspace + app UUID + API URL). For a worker, npm run dev in the scaffold (see [Run a worker locally]( run a worker locally)). A worker that calls the Cargo API also needs a CARGO API TOKEN secret before its first deploy (see [Worker env vars and secrets]( worker env vars and secrets)).
4. Deploy — hosting deployment create app uuid <uuid source <dir uploads the source. The backend then builds it in a sandbox: for an app, npm ci ignore scripts followed by the app's own build script, or the framework's default build if there is none (see [App builds]( app builds)); for a worker, it bundles the entrypoint. 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]( async polling)).
URLs
The live host is <slug <first 8 chars of the workspace UUID under the hosting root domain, and apps and workers have different root domains — in production https://<slug <ws .app.getcargo.run for an app, https://<slug <ws .worker.getcargo.run for a worker. The workspace suffix is what makes the host globally unique, which is why a slug only has to be unique inside your workspace.
Don't build the URL by hand. Read url from create or get — the root domain differs per environment. Each deployment also gets a preview host, https://deployment <deploymentUuid .<root , before it is promoted.
Because the roots differ, an app calling a worker is always a cross origin request. No option serves them on the same origin, so the worker has to answer CORS. See the app + worker pattern in [ references/examples/workers.md ](references/examples/workers.md calling a worker from an app).
Apps
Templates: blank (minimal starting point), territories overview (read only territories grid demoing useCargoApi() + react query), and public site (a public, indexable site that prerenders every route from its own build script and ships robots.txt + sitemap.xml ). Run app init <dir list templates for the current list.
App builds
If package.json declares a build script, Cargo runs it, and that script owns the whole build. Cargo runs nothing before or after it, so the script must produce the client bundle as well as anything extra, such as prerendered HTML. Without a build script, the detected framework's default command runs:
Framework (detected from dependencies) Default build Output dir
Vite, and anything undetected vite build dist
Astro astro build dist
Gatsby gatsby build public
Create React App react scripts build build
Parcel parcel build dist
Webpack webpack build dist
Next.js, SvelteKit and Nuxt are not detected : they fall through to the default ( dist ), so declare a build script that exports static files into dist/ . Every app receives all public env prefixes whatever the framework (see [App env vars are public]( app env vars are public)).
Output must land in that framework's output directory and contain an index.html , or the build fails. Unknown paths fall back to that shell.
Whatever the build script does now runs on every deploy. That includes a tsc pass, a different mode , and prebuild / postbuild hooks. A script that fails there fails the deploy. The live deployment keeps serving, because promotion only follows a successful build.
A build script Cargo can't use falls back silently. That covers an unparseable package.json , an empty script, or a non string entry. The deploy still goes green, and only the build log says why, so check it when a prerender step seems to have been skipped.
Platform values are injected under every public prefix. A Vite app reads VITE CARGO API URL and a Next.js app reads NEXT PUBLIC CARGO API URL . When a build script is used, they are also passed as real process env vars, so a plain Node prerender step sees them. User env values are redacted from build logs.
App env vars are public
An app reads only env vars whose key starts with a public prefix : VITE , NEXT PUBLIC , PUBLIC , NUXT PUBLIC , GATSBY or REACT APP . Any of these prefixes works whatever the framework. They come from workspace env vars and from app level entries ( hosting app env var set , POST /v1/hosting/env vars with "kind":"app" , or defineApp({ env }) in a project). An app entry overrides a workspace entry with the same key. Every one of them is compiled into a bundle anyone can download. For that reason:
App env vars cannot be secret. The API rejects isSecret: true with secretNotSupportedForApp , a project's defineApp throws on a secret() value, and secret workspace entries never reach an app build. Credentials belong in a worker that the app calls. hosting app env var set help shows a secret example anyway ( CARGO API TOKEN on an app) — it fails. The flag is shared with the worker command; on an app the backend refuses it, create and update alike.
Keys matching a platform key under any prefix ( CARGO API URL , CARGO WORKSPACE UUID , APP BASE PATH , …) are reserved.
Values are baked in at build time, so a change needs a new deploy + promote.
Custom domains and search indexing
Cargo owned hosts are noindex . The default .app.getcargo.run host and every deployment <uuid preview answer with X Robots Tag: noindex , so an app only becomes indexable on a custom domain . No CLI command attaches one at CLI 1.0.96, so use the API:
The hostname needs at least three labels , so attach www.example.com , not example.com .
Workers take custom domains too ( "kind":"worker","workerUuid":… ). Separate hostnames are still separate origins, so app → worker CORS still applies.
Indexing also needs real HTML per URL. Prerender in the build script (the public site template shows how). Link prerendered routes as .html paths: /about.html serves the prerendered file, while /about falls back to the SPA shell. Ship public/robots.txt + public/sitemap.xml , and put a title, description, canonical and Open Graph tags in each prerendered head.
A hosted app never returns a 404. An unknown path serves the SPA shell with a 200 , so a client side not found view should set <meta name="robots" content="noindex" itself.
Apps built on CargoRefineApp require a Cargo login and cannot be indexed. A public site renders its own tree.
Nothing is submitted to search engines for you. Submit the sitemap in Search Console.
Workers
Same command shape as apps — substitute worker for app :
Templates: blank (auto OpenAPI spec + Swagger UI) and custom integration (a Cargo Custom Integration — manifest / actions / extractors / autocompletes / dynamic schemas).
Entrypoint. The build bundles the first of src/index.ts , src/index.js , index.ts , index.js that exists, and fails if there is none. .mjs , .mts and .cjs entrypoints are not picked up, so rename them to .js / .ts ; ES module syntax works in .js because the scaffold's package.json sets "type": "module" .
Worker env vars and secrets
A worker reads configuration from c.env.KEY (Hono context) or the env argument to fetch(request, env) . Three sources feed it:
Source Set with Reaches
Platform bindings automatic CARGO API URL , CARGO WORKSPACE UUID , CARGO WORKER UUID
Workspace env vars cargo ai workspaceManagement envVar create key K [ secret] every worker in the workspace (apps get only the non secret, public prefixed ones)
Worker env vars cargo ai hosting worker env set worker uuid <uuid key K [ secret] , defineWorker({ env }) in a project, or POST /v1/hosting/env vars that worker only, and a worker entry overrides a workspace entry with the same key
CARGO API TOKEN is not injected by default. createCargoApi(c.env) throws Missing CARGO API TOKEN… until you provide one. The one exception: a worker with a service user set ( hosting worker update uuid <uuid run as user uuid <service user uuid , or defineWorker({ runAs }) ) gets a temporary CARGO API TOKEN for that user, minted fresh on every publish (preview deployments included; the previous one is revoked) unless its env already sets the key. worker create has no run as user uuid ; set it with update . The user must be a service user whose permissions yours cover ( runAsUserNotFound / runAsUserNotPermitted ) — creating one is in [ ../cargo workspace management/SKILL.md ](../cargo workspace management/SKILL.md) → "Service users". Otherwise mint a workspace API token and store it as a secret before the first deploy:
A workspace entry gives every worker that token. If only one worker should hold it, set it on that worker instead:
Project: defineWorker("my worker", { path, env: { CARGO API TOKEN: secret("CARGO API TOKEN") } }) .
CLI: printf %s "$CARGO API TOKEN" cargo ai hosting worker env set worker uuid <uuid key CARGO API TOKEN value secret (omit value to read $CARGO API TOKEN from the shell). hosting worker env list worker uuid <uuid shows the keys, with secret values as null ; hosting worker env remove <env var uuid deletes one (uuid from env list ), effective on the next deploy + promote.
API: POST /v1/hosting/env vars with {"kind":"worker","workerUuid":"<uuid ","key":"CARGO API TOKEN","value":"…","isSecret":true} . From TypeScript that is api.hosting.envVars.create(…) in @cargo ai/ap