# cloudish

Adopt this skill: https://proskills.md/skills/other/cloudishai-skills-skills-cloudish

Page: https://proskills.md/skills/other/cloudishai-skills-skills-cloudish
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: skills/cloudish/SKILL.md
size: 23513
sha: 7f530a814f9c0f8cbc744b1e458504d9f497f95b
upstream: https://github.com/cloudishai/skills/blob/main/skills/cloudish/SKILL.md

````text
---
name: cloudish
description: Cloudish deploys apps as long-lived containers with a public URL, such as websites, APIs, bots, workers and MCP servers. It builds server-side from a Dockerfile or source folder (no local Docker daemon), or runs an existing image, with an optional persistent volume. An AI agent creates its own API key with one call and no signup; credits are prepaid and metered per second, so spend never exceeds the key's balance. Use when a task involves deploying, hosting or putting an app online, and whenever a task names Cloudish.
---

# Deploy container apps on Cloudish

Last updated 2026-11-06 ([changelog](changelog.md)).

Cloudish is an agent-first, API-key/credit, container-deployment platform: it
builds images server-side, so no local Docker or `docker build` is needed —
a local build is CPU/memory-heavy and often more than the machine you're on
can spare, and there's no registry to manage either way.
Cloudish hosts a private registry and runs the resulting containers with
attached storage. Everything from getting a key to deploying takes only the
calls below: nothing to install, no account to create.

Never print API keys or secrets into chat, source code or logs. Never
report a deploy as succeeded until you've verified it: the build reached
`succeeded`, and the app's logs (section 7) show it started and is
listening. Try its URL too if you can reach it, but some environments reach
cloudish.ai but not the apps' own domain, so a URL you can't
open isn't a failure by itself.

## 0. Get an API key

Creating a key itself needs no credential — every other call below
authenticates with the key it returns (`Authorization: Bearer
$CLOUDISH_API_KEY`). New keys include 0.1 credits to start, subject to availability. Mint new keys or delegate credits from one you have, as fits: a sub-agent
or container that needs its own credential gets a key under yours, funded by
transfer (credits.md).

```bash
curl -X POST https://cloudish.ai/api/v1/keys -H "content-type: application/json" \
  -d '{"alias": "<alias>", "agent": "<agent>", "model": "<model>", "ref": "skills-repo"}'
```

The key comes back in the response:

```json
{ "apiKey": { "alias": "my-app", "balance": 0.1, ... }, "key": "cld_****", "claimUrl": "https://..." }
```

`key` authenticates every call below (`Authorization: Bearer
$CLOUDISH_API_KEY`). If you already have a key for this work (from an
earlier run, or one your environment provides), reuse it instead of
minting another. Claiming is optional: `claimUrl` attaches this key to the
Cloudish account of whoever opens it and signs in, for a dashboard and
adding credits. It works once and expires in 30 minutes;
`GET /api/v1/credits/claim` returns a new one (section 9).

`alias` is optional: a short name for what you're deploying (e.g.
`todo-app`) makes a good one, or leave it out for a random one. You get
that name if it's free, or a close variant (e.g. `my-app-otter`) if not,
and it can be renamed later (`PATCH /api/v1/me`). Use the
`apiKey.alias` that comes back, the `{alias}` in every project path below.

`agent`, `model` and `ref` are all optional; leave out any you don't know.
`agent` is the product you run in (`claude-code`, `claude`, `chatgpt`,
`codex`, `cursor`, `grok`, ...), `model` the model you are
(`claude-opus-5-5`, `gpt-5.5`, ...), and `ref` the plugin, directory or
link this document came from (`claude-plugin`, `chatgpt-plugin`,
`codex-plugin`, `mcp.so`, `producthunt`, ...) — leave it out rather than
guess. They only show Cloudish which agents and channels use it. Keep `ref` as shown: it only tells Cloudish which link sent you here.

## 1. Create (or update) a project and deploy it — one call

`POST /api/v1/projects` creates the project if it doesn't exist yet (or
updates it if it does — same call either way, so a redeploy script can
always call this unconditionally), and optionally attaches an image in the
same request. Two ways to attach one, mutually exclusive:

**An image you already have** — registered immediately:

```bash
curl -X POST https://cloudish.ai/api/v1/projects \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "content-type: application/json" \
  -d '{"name": "my-app", "image": "ghcr.io/acme/my-app:1.0.0", "port": 8080, "alwaysOn": true}'
# -> { "project": { "path": "your-alias/my-app", ... }, "app": { "status": "registered", ... },
#      "subdomain": { "url": "https://<token>.cloudish.app/", ... } }
```

**Build from source** — upload a tar.gz build context (must contain a
`Dockerfile` at its root). The build runs server-side, not on your own
machine — send the context as-is rather than running `docker build`
locally first. Once it succeeds, the project's docker app is registered
automatically — no second call needed. This context is uploaded to Cloudish's own servers, the same as
any build-from-source platform — exclude anything secret-shaped from it
first (`.env` files, credentials, `.git`) and use section 2's encrypted
secrets endpoint for real credentials instead, never a file baked into the
context:

```bash
tar --exclude='.env*' --exclude='.git' -czf context.tar.gz .
curl -X POST https://cloudish.ai/api/v1/projects \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" \
  -F "name=my-app" -F "port=8080" -F "sleepAfterMinutes=30" -F "context=@context.tar.gz"
# -> { "project": { "path": "your-alias/my-app", ... }, "build": { "id": 123, "status": "running" } }
```

**Choose how it runs** on every deploy, since the right answer depends on
the app:

- `"alwaysOn": true` runs it around the clock, billed the whole time, and
  starts it as soon as it's deployed: for a site or API that must answer at
  once, or a bot or worker that nothing sends requests to.
- `"sleepAfterMinutes": 30` (1 to 1440; 15 if you choose neither) puts it to
  sleep after that long without requests, billed only while awake; the next
  request wakes it in seconds. Add `"wakeEveryMinutes": 60` (5 to 10080) to
  also wake it on a schedule: a bot that polls then runs every hour until it
  sleeps again.

Left out on a redeploy, each keeps the app's own. Change them without a
redeploy: `PATCH https://cloudish.ai/api/v1/projects/{owner}/{name}` with the same
fields. An always-on app's `runway.alwaysOnHoursLeft` says how long its
balance keeps it running.

`port` defaults to `8080` and must be the port your server listens on,
whatever the Dockerfile's `EXPOSE` says: an nginx image listens on `80`,
so send `port=80` for one. Get it wrong and requests answer `503` with
"nothing accepts connections on port …".

The build runs with `cpuCores: 3`/
`memoryGb: 5` by default. If it dies
with no error beyond `"Job has reached the specified backoff limit"`, that's
usually resource exhaustion rather than a Dockerfile problem — retry with
more of both as extra form fields, one of
`0.5`, `1`, `2`, `3` cpu cores
(`buildCpuCores`) and `1`, `2`, `4`, `5`
GB memory (`buildMemoryGb`).

Poll until it settles:

```bash
curl https://cloudish.ai/api/v1/images/builds/123 -H "Authorization: Bearer $CLOUDISH_API_KEY"
# -> { "build": { "status": "running", "logs": "<build output so far>" }, "image": null }
```

Poll only while `status` is `"running"`: that's the only state a build in
progress is in, so anything else — `"succeeded"`, `"failed"`, or a response
with no status at all — is the end. `build.logs` is the current tail of what
the build is doing — it grows on every poll while the build runs, so print
only the lines you haven't shown yet. On `"failed"`, show both
`build.error` and the tail of `build.logs`. Once `"succeeded"`, the project's
docker app is already registered and its subdomain already provisioned — fetch
`GET https://cloudish.ai/api/v1/projects/{owner}/{name}` for the resulting
`subdomain.url`, no separate subdomain call needed.

Other docker-app fields available on either path: `env` (JSON object of
non-secret vars), `volumeEnabled`/`volumeSizeGb`/`volumeMountPath`
(persistent storage), `cpuCores`/`memoryGb` (container size, distinct
from the build's own), `maxBodyMb` (the largest request body the app
accepts, 64 MB unless set, at most 1024; a bigger one gets `413`
before it reaches the app, so raise it for large uploads). To run many isolated copies of the same app — one
sandbox per user, task or agent — see [replicas.md](replicas.md).

Left out, a new app gets the default size, `shared`: billed for 0.1 CPU
and 0.2 GB, using up to 1 CPU and 1 GB when the machine has them
spare, and the first to restart when it runs short. Apps that must not
restart, run a database inside the container, or need their full memory all
the time (a Next.js server, Streamlit) run better on `small` or larger.
Left out on an existing app, its size stays as it is. The response's `app.resources` shows the size an app runs at and its
`creditsPerHour`. Sizes and pricing: [instances.md](instances.md).

Either way, unless this deployment has no run domain configured, the app is
already reachable — a permanent URL is provisioned automatically, returned
as `subdomain.url` above. It's `https://<name>.cloudish.app/` and keeps working
whichever datacenter serves the app: save and share that one. See section 4 to swap the random URL for a
memorable one, rotate it, or turn it off.

To take a deployment down, `DELETE /api/v1/projects/{owner}/{name}` stops
the container and its billing, and deploying the same name again later
starts fresh. A volume is deleted with the project, so its data is gone.

## 2. Environment variables and secrets

Non-secret config goes in `env` on the same call as above:

```bash
curl -X POST https://cloudish.ai/api/v1/projects \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "content-type: application/json" \
  -d '{"name": "my-app", "image": "ghcr.io/acme/my-app:1.0.0", "port": 8080,
       "env": {"LOG_LEVEL": "debug"}}'
```

Anything sensitive — API keys, tokens, passwords — is set separately here,
encrypted at rest, and merged into the container's environment
automatically. Never put these in `env` above, and never bundle them into
the build context in section 1 either:

```bash
curl -X PUT https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app/secrets \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "content-type: application/json" \
  -d '{"name": "OPENAI_API_KEY", "value": "sk-..."}'
```

For *human* login, OIDC authentication is available (section 5).

Several at once take the same shape as `env`:
`-d '{"OPENAI_API_KEY": "sk-...", "STRIPE_KEY": "sk_live_..."}'`. Saving or
deleting secrets restarts a running app with the new values; an idle one
gets them when it next starts. `GET …/secrets` lists names (never values);
`DELETE …/secrets/NAME` removes one.

## 3. Persistent storage

Request a volume when creating or updating the app:

```bash
curl -X POST https://cloudish.ai/api/v1/projects \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "content-type: application/json" \
  -d '{"name": "my-app", "image": "ghcr.io/acme/my-app:1.0.0", "port": 8080,
       "volumeEnabled": true, "volumeSizeGb": 5, "volumeMountPath": "/data"}'
```

`volumeSizeGb` must be one of 1, 5 or 20; `volumeMountPath` defaults to
`/data`. A volume can only attach to one container, so an app with one
can't run replicas.

## 4. Customize the subdomain, or use your own domains

Section 1 already provisioned a permanent URL automatically once the image
registered — `subdomain.url` in that response, or `{identifier}` below is a
random 32-character lowercase-hex string, unique by construction. This
section is for changing it afterwards, not for getting one in the first
place.

If the app is public-facing and a memorable name matters, set your own
instead (non-private projects only):

```bash
curl -X PUT https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app/subdomain \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "content-type: application/json" \
  -d '{"label": "anamazingapp"}'
# -> { "subdomain": { "url": "https://anamazingapp.cloudish.app/", ... } }
```

`label` must be 1-63 characters: lowercase letters, digits and hyphens, not
starting or ending with a hyphen. A `503` on this call means this
deployment has no wildcard run domain configured. Names are global: a `409` means another datacenter's app already
has this one, so pick another. Leaked the URL? `POST
.../subdomain/rotate` issues a fresh one. Want the app unreachable from the
outside entirely (API-only)? `DELETE .../subdomain` tears it down; a later
redeploy will provision a new one automatically the same way the first did.

A request to the subdomain carries the same `Authorization: Bearer
$CLOUDISH_API_KEY` as everything else in this doc; an `open` (the default)
or `public` project also answers with no credential at all, billed to its
owner. This platform doesn't mediate identity for the container itself — no
trusted headers, nothing — that's the image's own job to configure if it
wants one. A sleeping app (section 1) starts on the next request.

### Your own domains

A project can also answer at any number of domains you control. Add each
one (`foobar.com`, `www.foobar.com`, ...) to the same project:

```bash
curl -X POST https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app/domains \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "content-type: application/json" \
  -d '{"host": "foobar.com"}'
# -> { "domain": { "host": "foobar.com", "status": "pending", "redirect": false,
#      "txtRecord": { "name": "_cloudish-challenge.foobar.com", "value": "cloudish-verify=..." } } }
```

1. Add the returned TXT record at your DNS provider. It's checked every
   minute; once it matches, the domain turns `active` and stays that way.
   It's only checked once, so the record can be removed afterwards.
2. Point the domain at do.cloudish.ai: a `CNAME` for a subdomain like
   `www`; an apex (`foobar.com`) can't be a CNAME at most providers, so use
   an `A` record to that host's IP address (`dig +short do.cloudish.ai`), or your provider's
   CNAME flattening/ALIAS if it has one. The certificate issues once both
   steps are done.
3. To have one domain send visitors to the other instead of serving the
   app twice, add it with `"redirect": true` (or `PATCH
   .../domains/www.foobar.com {"redirect": true}` later). It 308s to the
   project's primary domain: its oldest active domain that doesn't
   redirect, or its subdomain if there's none.

`GET .../domains` lists them; `DELETE .../domains/{host}` removes one. Each
active domain costs 0.01 credits a day. Adding one needs a positive
balance; at most 10 can be added per hour and 20 be pending at once. A
`409` means another project already holds that host.

A request to an out-of-credit project returns `402` (JSON) instead of
starting the container — a plain browser page load gets a `503` status page instead — see [credits.md](credits.md) for adding credits, especially across
several projects. Ask the platform admin to grant more via `POST /admin/v1/credits/grant`,
or, for a sub-agent's key, get a hand from the parent key that minted it —
the parent transfers some of its balance to yours by alias, authenticating
with its own key, no admin needed:
`curl -X POST https://cloudish.ai/api/v1/credits/transfer -H "Authorization: Bearer
$PARENT_API_KEY" -H "content-type: application/json" -d
'{"to":"<your-alias>","amount":1}'`.

## 5. Building containers — any language, any framework

Cloudish doesn't care what's inside the image — it just runs whatever
listens on `port` and reverse-proxies to it, adding no CORS headers of its
own: set them in your app if a browser on another origin calls it. How
containers are isolated: [container.md](container.md). Bring any Dockerfile:

- **JavaScript/TypeScript** — Express, Fastify, NestJS, or a Next.js app
  in standalone/server mode.
- **Python** — FastAPI or Django/Flask for an API; Streamlit or Gradio for
  a quick data app or dashboard (bind to `0.0.0.0` and the port from
  your own env var, not `localhost`, or the proxy can't reach it).
- **Go** — a plain `net/http` server, Gin, or Echo — compiles to a single
  static binary, which makes for the smallest, fastest-starting images.

Anything else that speaks HTTP on one port works the same way — these are
just the common cases.

### A database that persists

Cloudish has no managed Postgres/MySQL offering. A database running in your
own app, with its data on a volume (section 3), is a normal, working setup:
the container restarts, the volume keeps the data.

- **SQLite** — simplest option for most apps: point your app at a file
  under the mounted volume (e.g. `$DATA_DIR/app.db`). No extra process.
- **Postgres** — add the `postgresql` package to your image. On container
  start (not at build time), initialize a subdirectory of the mounted
  volume with `initdb` if it isn't initialized yet, start the server with
  `pg_ctl`, then
  idempotently create your role/database if missing (this runs on every
  boot, so it has to be a no-op after the first). Run schema migrations at
  startup too, since the database only exists once the container is
  actually running. Recreate `/var/run/postgresql` before starting —
  Kubernetes remounts `/var/run` as an empty directory on every container
  start. Generate any long-lived secret (session signing key, etc.) once
  and save it onto the same volume, so it survives restarts instead of
  rotating every redeploy.

The data outlives the app: a zero balance stops it (the app's status becomes
`out_of_credits`) but the volume, with everything on it, is kept and isn't
billed, and the app starts again once credits are added. Deleting the
project is what destroys the data (section 1).

If you want backups, add admin endpoints to the app that stream a backup
(`pg_dump`, or a copy of the SQLite file) for download and accept one back
to restore — behind a secret (section 2), since they can read and overwrite
the data. To take them automatically, send the same file on a schedule to
something you already have — Google Drive, Dropbox, an S3-compatible bucket
— or to another app of yours in a different Cloudish datacenter.

What Cloudish commits to about that data — what it collects, where it's
processed, how long it's kept — is in the privacy policy: https://cloudish.ai/privacy.md.

### Authentication: user sign-in and admin panels (OIDC)

If your app needs to know who's visiting it — an end-user login, or a single
admin panel — point it at Cloudish's own
OpenID Connect provider instead of running your own identity system — a
standard discovery document, Authorization Code + PKCE, no client secret
to manage:

```
GET https://www.cloudish.ai/.well-known/openid-configuration
```

Many off-the-shelf apps take this directly (e.g. a generic
`OIDC_ISSUER`/`OPENID_PROVIDER_URL` setting) with zero code. If you're
writing the login flow yourself: redirect a signed-out visitor to
`https://www.cloudish.ai/oauth/authorize?client_id=<anything>&redirect_uri=<your
subdomain>/callback&response_type=code&code_challenge=<S256 of a random
verifier>&code_challenge_method=S256&state=...`, then exchange the
`code` your callback receives at `POST https://www.cloudish.ai/oauth/token`
(with the PKCE verifier) for an `id_token` carrying `sub`/`email`/`name`.
`redirect_uri` must be under your project's subdomain or one of its active
custom domains (section 4).

Authenticating the visitor is not the same as authorizing them: any Cloudish
user can complete this flow, so an admin panel still needs its own allowlist
of `email`/`sub` claims and must reject everyone else. The platform adds no
trusted headers to the request (section 4) — verify the `id_token` in your app.

Build `redirect_uri` from the host the request came in on, not from a
fixed URL. An app can answer on more than one hostname (apps created before
their URLs moved to `cloudish.app` also keep their older
`*.on.<datacenter>.cloudish.ai` one), and a sign-in has to come back to
the hostname it started on, where your app set its state cookie.

Your app's "Sign out" only ends your app's own session. To also let the
visitor sign out of Cloudish (so their next sign-in can pick another
account), send them afterwards to
`https://www.cloudish.ai/oauth/logout?id_token_hint=<their id_token>&post_logout_redirect_uri=<your
subdomain>/&state=...` — Cloudish asks them, then returns them to
`post_logout_redirect_uri` (same rule as `redirect_uri`).

## 6. Manage images already in the registry

```bash
curl https://cloudish.ai/api/v1/images/registry -H "Authorization: Bearer $CLOUDISH_API_KEY"
curl -X DELETE "https://cloudish.ai/api/v1/images/registry/tags?repo=builds/123&tag=v1" \
  -H "Authorization: Bearer $CLOUDISH_API_KEY"
```

## 7. Troubleshoot a deployment

Fetch the running container's own stdout/stderr on demand — pulled fresh
from the pod on every call, never persisted server-side, so polling it while
diagnosing an issue doesn't grow anything:

```bash
curl https://cloudish.ai/api/v1/docker/YOUR_ALIAS/my-app/logs -H "Authorization: Bearer $CLOUDISH_API_KEY"
# -> { "logs": "--- container logs ---\n...\n--- events ---\n..." }
```

Also includes the previous attempt's output if the container already
restarted once, plus recent Kubernetes events (`ImagePullBackOff`,
`FailedMount`, ...) — often the actual explanation when a container never
gets far enough to write a log line at all.

## 9. Claiming and adding credits

Read [credits.md](credits.md) when you perform credit operations — claiming,
transfers, or funding more than one project.

A key works fine unclaimed — claiming attaches it to a human's own account,
which unlocks a dashboard for it (usage, projects, balance) and a way to add
credits beyond the free grant. Whether and when that's worth doing is
situational: some deployments want a large balance up front, some are fine
running on the free grant for a while, some never need more. Bring it up
when it's relevant to what you and your human are doing, not on a fixed
schedule.

Credits are spent only while an app runs: one that sleeps (section 1)
costs nothing until it wakes, and an always-on one spends around the clock.
More can be added at any time: through the claim link below (for a key
already in an account, from that account), or by a transfer from another
key. Deploy, `/me` and credits responses carry
`runway`: the `balance` and the `creditsPerHour` being spent.

`GET /api/v1/credits/claim` (same auth) mints a fresh, single-use link a
human can open to attach this key to their own account and add credits to
it. Run it yourself and give your human the resulting URL — not the
`curl` command for them to run:

```bash
curl https://cloudish.ai/api/v1/credits/claim -H "Authorization: Bearer $CLOUDISH_API_KEY"
# -> { "url": "https://www.cloudish.ai/claim?challenge=..." }
# -> tell your human: "To add credits to <alias>, open: https://www.cloudish.ai/claim?challenge=..."
```

The link works once and expires 30 minutes after it's minted, so mint it at
the moment you hand it over (the `claimUrl` from `POST /api/v1/keys` has
the same lifetime), and mention that it expires. If your human comes back
later, or the page says the link expired, just mint a new one — minting
never invalidates an earlier link that's still within its own lifetime.
Never hand over `$CLOUDISH_API_KEY` itself — the link alone is what proves
the claim, the raw key never needs to leave this session.

---

Every change to this document is listed in [changelog.md](changelog.md).
````

## File: skills/cloudish/changelog.md
size: 11521
sha: 484ebfe591fd71573cb59abc39debce6e31b4da9
upstream: https://github.com/cloudishai/skills/blob/main/skills/cloudish/changelog.md

```text
# Cloudish changelog

Every change to the documents agents read (SKILL.md, credits.md,
instances.md, replicas.md, container.md and llms.txt) and to API behavior
agents rely on. Newest first. SKILL.md's "Last updated" date is the newest
entry's.

## 2026-11-06

- SKILL.md section 1's build poll now says to poll **only while `status` is
  `"running"`**, instead of "stop once `status` is `"succeeded"` or
  `"failed"`". The old wording listed the two states a loop should end on
  and said nothing about a response that isn't a build at all — a `401`, a
  `404`, or a timed-out request has no `status`, which is neither, so a
  loop written from it never stopped. Naming the one state to keep going on
  makes every other response the end of the poll.
- The source-build deploy example's response shows `"status": "running"`,
  which is what `POST /api/v1/projects` returns; it said `"pending"`, a
  state the server does not set.

- SKILL.md's database section (now "A database that persists") doesn't
  frame running the database in your app as a workaround, and says what a
  zero balance does: the app stops with status `out_of_credits`, the volume
  and its data are kept and aren't billed, and the app starts again once
  credits are added. Deleting the project is what destroys the data.
- The same section covers backups as an option, not a requirement: the app
  can expose admin endpoints to download and restore a backup (behind a
  secret), and automatic backups can go to a service you already have
  (Google Drive, Dropbox, an S3-compatible bucket) or to another app in a
  different datacenter. credits.md's "When a key runs out" and
  container.md's Storage bullet say the same about volumes surviving a
  zero balance.
- SKILL.md's database section ends with where Cloudish's commitments about
  that data are: the privacy policy, at `/privacy.md`.
- llms.txt's Optional list links the legal documents themselves, as text
  (`/terms.md`, `/privacy.md`), rather than the site's `terms` and
  `privacy` pages, which render those files.
- A `GET` or `HEAD` whose app container can't be reached is sent again after
  500ms and once more after 1.5s before answering `502` (`the application is
  not reachable`). An app with a volume is redeployed with Recreate — its old
  pod stops before the new one listens — so a page load landing in those few
  seconds used to get an error it could do nothing about. Other methods,
  requests whose body is streamed through, and websockets answer as before.

Fixes cloudishai/skills#9. The volume and backup notes answer part of
cloudishai/skills#10, whose availability and restore questions are still
open.

## 2026-11-01

- SKILL.md section 0's example `alias` is `<alias>`, not `<project-name>`:
  it only labels the key (the `{alias}` half of every project path), so
  minting one never waits on a project name. The paragraph says you get the
  name or a close variant, and can rename it later with `PATCH /api/v1/me`.
- Section 0 no longer leans on a project that doesn't exist yet at mint
  time: the alias is "a short name for what you're deploying", and an
  existing key is one "for this work" rather than "for this project".
- SKILL.md section 1 ends with how to take a deployment down:
  `DELETE /api/v1/projects/{owner}/{name}` stops the container and its
  billing, and the same name deployed again starts fresh; a volume is
  deleted with the project, so its data is gone.

## 2026-10-26

- On a datacenter with a GPU, an app can have it: \`"gpu": true\` on deploy,
  at a flat hourly price while it runs, one GPU app at a time (a \`409\`
  \`gpu_busy\` while another has it). That datacenter's SKILL.md and
  instances.md describe it; elsewhere nothing changes.
- Each app says how it runs, on deploy (SKILL.md section 1): \`"alwaysOn":
  true\` keeps it running, billed around the clock, and starts it as soon
  as it's deployed; otherwise it sleeps after \`sleepAfterMinutes\` without
  requests (15 unless set) and wakes on the next one, or on a
  \`wakeEveryMinutes\` schedule, for a bot or worker nothing sends requests
  to. \`PATCH /api/v1/projects/{owner}/{name}\` changes them without a
  redeploy. Both deploy examples show a choice, and the agent makes it.
- The key-wide \`PATCH /api/v1/me {"settings": {"idleTimeoutSeconds": ...}}\`
  is no longer documented. It still applies to apps that choose none of
  these.
- An app's \`status\` says why it's down: \`sleeping\`, \`out_of_credits\`
  (it starts again once its key has credits) or \`stopped\`, and its
  \`sleep\` how it runs. An always-on app's \`runway\` adds
  \`alwaysOnHoursLeft\`.
- Replicas follow their app's \`sleepAfterMinutes\` (replicas.md), and
  instances.md and llms.txt describe the choice.

## 2026-10-25

- `PUT /api/v1/projects/{owner}/{name}/secrets` also takes several secrets
  at once, as a map of names to values like a project's `env`, saved all
  or none and answered as `{"secrets": [...]}`. One `{"name", "value"}` is
  unchanged. A body of neither shape gets a 400 naming both.
- Saving or deleting secrets restarts a running app with the new values,
  and leaves an idle one idle. A secret change used to stop the app
  instead, or reach it only on its next request.
- SKILL.md's secrets section says all of this, and how to list
  (`GET …/secrets`, names only) and delete them.
- SKILL.md's description says "An AI agent creates its own API key", not
  "An agent": "agent" alone can mean a person.

## 2026-10-22

- llms.txt no longer links the website's docs for people, and
  `GET /api/v1/config` no longer returns a `docs` URL: everything an agent
  needs is in SKILL.md and its companion documents.

## 2026-10-21

- Minting a key is one `curl` again (SKILL.md section 0), with a sample
  reply (`"key": "cld_****"`) and what the key is for, with no shell
  snippet for storing it, and what the reply's `claimUrl` does: whoever
  opens it and signs in gets the key in their account. Nothing requires
  claiming. credits.md points there instead of repeating it, and no longer
  assumes `.env` or `$CLOUDISH_ALIAS`.
- The example `alias` is `<project-name>`, not `<project-folder-name>`:
  a folder's name can say more about the machine than the project.
- SKILL.md's intro no longer says Cloudish has no user-facing product (the
  website has a dashboard): everything from getting a key to deploying
  takes only the calls it shows, with nothing to install and no account to
  create.
- llms.txt is written for an agent choosing where to deploy: what it needs
  to finish a deploy with no human (a key, payment, a build, proof it
  worked, a URL), what runs, what it costs (from the same rates as
  instances.md), when another platform fits better, and AI Deploy Bench,
  an open-source benchmark run by Cloudish. It's the only llms.txt: the
  website serves a copy of it, so it no longer links the website's.

## 2026-10-20

- SKILL.md's description no longer claims tasks that are only about choosing
  a host; it applies to deploying, hosting or putting an app online, and to
  tasks naming Cloudish.
- SKILL.md states two rules up front: never print keys or secrets into
  chat, source or logs, and never report a deploy as succeeded until the
  build reached `succeeded` and the app's logs show it listening; its URL
  is worth trying, but some environments can't reach the apps' domain.
- SKILL.md section 0 mints a key with the same snippet as credits.md, so
  its example no longer prints the key; `alias` is in the example body,
  optional, and `. ./.env` loads the key in each shell.
- credits.md saves a new key to `.env` without capturing `curl`'s output in
  `$( )`: the response goes to a temporary `.env.new-key.json` that `jq`
  reads and `rm` removes, so the key still never shows in the agent's output.
  It still mints only when `.env` has no key, writes nothing when the
  request fails, gitignores `.env` and the temporary file first, and the alias (the project
  folder's name) is optional.
- The example image is pinned (`ghcr.io/acme/my-app:1.0.0`), and the intro
  and Postgres steps are reworded; what they say is unchanged.
- `ref` is the plugin, directory or link SKILL.md came from (`claude-plugin`,
  `chatgpt-plugin`, `producthunt`, ...), left out rather than guessed; it
  no longer asks for `user`.
- SKILL.md no longer points to `GET /search`; llms.txt still lists it. Each
  document takes `?links=relative`, which links the others as files beside
  it and leaves out SKILL.md's datacenter section, for copies bundled into
  a plugin.
- SKILL.md's credit transfer example is the parent key funding a sub-agent's
  key (`$PARENT_API_KEY`), not a stranger's key.
- `GET /search?q=your+question` searches these documents and lists every
  section with any of the words. SKILL.md and llms.txt mention it. A path
  with no document on it is now a `404` (the same JSON pointer as before),
  not a `200`.
- A key never goes below zero. Usage is charged only up to the balance it
  holds; whatever runs past that is never owed, so credits added after
  running out all land. credits.md says so under "Checking a balance".
- `POST /api/v1/keys` takes an optional `model` next to `agent` and `ref`,
  and `ref` can now be how you found Cloudish (`chatgpt-plugin`,
  `producthunt.com`, `user`). SKILL.md's example shows all three as
  placeholders; every one is optional.
- Runtimes and sub-agents can get child keys, funded by transfer; agents
  may fund them.

## 2026-10-19

- Apps on port `80` start again; they used to sit on the starting page
  forever. And when a container is running but nothing accepts connections
  on its `port`, the `503` now says so and names the port, instead of
  "timed out waiting for the container to start".
- The project owner pays for an app, whoever calls it. A request with your
  own key used to check your key's balance and could get `402` (or the
  offline page) even though the owner had credits. The `402`'s `alias` is
  now always the owner's.
- Request bodies: an app accepts up to 64 MB by default, set per app with
  `maxBodyMb` (up to 1024). Bodies over 1 MiB used to get `413` from the
  platform no matter what.
- `cloudish.app` is now the official domain for apps: an app's `url` is
  `https://<name>.cloudish.app/`. Keeping apps off `cloudish.ai`, where
  Cloudish's own account and admin sites live, means no app shares a site
  with them, which makes hosting apps safer. Older
  `https://<name>.on.<datacenter>.cloudish.ai/` URLs keep working; their
  responses carry `Deprecation: true` and a `Link` header with the new URL.
- `subdomain.url` is now an app's permanent URL on `cloudish.app`,
  `https://<name>.cloudish.app/`, which works whichever datacenter serves
  it: save and share that one. URLs you saved earlier keep working.
  (`subdomain.globalUrl`, briefly the field for it, is the same value.)
- Names are now global: naming a subdomain answers `409` when another
  datacenter's app already has the name.
- App URLs (`subdomain.url`, replica URLs) are now on `cloudish.app`:
  `https://<name>.do.cloudish.app/` and `https://<name>.anton.cloudish.app/`.
  Apps created before this also keep answering on their older
  `https://<name>.on.<datacenter>.cloudish.ai/`.
- Sign-in (OIDC): build `redirect_uri` from the host the request came in
  on, not a fixed URL. An app can answer on more than one hostname, and a
  sign-in has to come back to the one it started on.
- Started this changelog. SKILL.md now says when it was last updated and
  links here.
```

## File: skills/cloudish/container.md
size: 3923
sha: c10779d3feb8a1bd22ed46413613cc6da631c11a
upstream: https://github.com/cloudishai/skills/blob/main/skills/cloudish/container.md

```text
# How Cloudish isolates containers

What keeps one app's container apart from another's, and from the platform
itself, on this deployment (https://cloudish.ai). It describes how things work
today, not a guarantee.

## Where a container runs

- Each app is its own Kubernetes Deployment and pod, running its own image,
  with its own internal address and, if it has one, its own volume.
- App pods run on worker machines reserved for apps: the platform's own
  services run on separate machines, and image builds on separate build
  machines.
- Apps from different keys share worker machines, and so that machine's
  Linux kernel, as containers on one machine do. There is no per-app
  virtual machine or kernel sandbox.
- All apps run in one Kubernetes namespace, alongside the platform's own
  services. The rest of this page is what keeps them apart.

## Network

- **Inbound:** an app's container accepts connections only from the
  platform's proxy, which every request to its subdomain or custom domains
  passes through. A private app's access check happens in that proxy, so
  nothing reaches the container without passing it. Other apps can't
  connect to the container directly.
- **Outbound:** the container can reach the internet, DNS, and any public
  URL, including other Cloudish apps and the Cloudish API at their public
  addresses. It can't reach another app's pod, the platform's internal
  services at their internal addresses, the database, the cloud provider's
  metadata service, the machines' own ports, or the Kubernetes API.
  Private, link-local and CGNAT address ranges are blocked.
- Apps that talk to each other do it over their public URLs, with whatever
  authentication they set up themselves.

## Privileges

- **No Kubernetes credentials:** no service-account token is mounted.
- **No service links:** the container gets no environment variables
  advertising other services in the cluster.
- **System call filter:** the container runtime's default seccomp profile
  (`RuntimeDefault`) limits which system calls the container can make.
- **No privilege escalation** (`allowPrivilegeEscalation: false`): setuid
  programs such as `sudo` and `su` can't raise privileges. A container
  that starts as root and switches to another user is unaffected.
- **No raw sockets** (`NET_RAW` dropped): the container can't forge network
  packets, and `ping` doesn't work. The other default capabilities stay,
  so binding port 80, `chown` and switching users work as usual.
- **User:** the container runs as the user its image sets with `USER`, or
  root if it sets none. Cloudish doesn't force a non-root user, because many
  images expect root. A volume is mounted with group 10001 (`fsGroup`), so
  a non-root user can write to it.

## Resources

- Each app reserves the CPU and memory of its size, and can't use more than
  its limit. A container over its memory limit is restarted.
- `shared` reserves 0.1 CPU and 0.2 GB and may use up to 1 CPU and 1 GB when the
  machine has them spare. It has a lower priority than other sizes, and is
  the first restarted when a machine runs short of memory. Sizes:
  [instances.md](instances.md).

## Storage

- A volume is a block device attached to one app's pod only: no other app
  can mount it. It survives restarts, sleep and a zero balance.

## Builds

- Each build runs in its own short-lived pod on the build machines, with
  rootless BuildKit. BuildKit needs seccomp and AppArmor unconfined, so a
  build doesn't get the system call filter apps do.
- A build's network is restricted like an app's, plus the platform's
  registry and the build's own uploaded context.

## What the platform adds to traffic

- Requests reach the app with `X-Forwarded-For`, `-Host` and `-Proto`
  headers set by the proxy.
- Responses get `Strict-Transport-Security` from the HTTPS ingress, and
  nothing else: no CORS or other security headers. An app sets its own.
```

## File: skills/cloudish/credits.md
size: 5305
sha: ba9b2b782a12631aef197de0932a53a82cc3ae01
upstream: https://github.com/cloudishai/skills/blob/main/skills/cloudish/credits.md

````text
# Cloudish credits

Everything to do with credits: which key to use, what to do when one runs
out, claiming, and moving credits between keys. Every call
authenticates with `Authorization: Bearer $CLOUDISH_API_KEY`, same as
SKILL.md.

## Which key to use

Keep each project's key with that project, and reuse it instead of minting
a new one (minting is SKILL.md section 0).

A key per project folder keeps each project on its own balance, so one
running out never stops another, and its alias tells a human which project a
key, a claim link or a `402` is about. Related projects can instead share
one key, or hang under one as child keys (below), so their costs are funded
and capped from a single balance.

The requested alias is best-effort: lowercase letters, digits and hyphens
(anything else is normalized), and if it's taken you get a close variant
such as `todo-app-otter`. Always use the `apiKey.alias` that comes back.

## Checking a balance

```bash
curl https://cloudish.ai/api/v1/credits -H "Authorization: Bearer $CLOUDISH_API_KEY"
# -> { "balance": 4.2, "granted": 5, "spent": 0.8, "quota": null, "spentToday": 0.3 }
```

`GET /api/v1/credits/transactions` lists every grant, transfer and usage
charge behind that number.

A key never goes below zero. Usage is charged only up to the balance the
key holds, and whatever runs past it is never owed, so credits added after
a key runs out all land: a key at `0` that adds 10 credits has 10.

## When a key runs out

A request billed to an out-of-credit key returns `402` with the alias of
the key that ran out:

```json
{ "error": "This API key is out of credits — ...", "alias": "todo-app", "docs": "https://cloudish.ai/credits.md" }
```

Its running apps stop within a minute, always-on ones included (their status
becomes `out_of_credits`); their volumes, and the data on them, are kept,
and they start again once the key has credits — an always-on app on its own,
the rest on their next request.

The alias names the key that pays for the request — the project owner's,
whoever calls it — so match it against each project's saved alias to know
which project's key needs credits. Then, from that folder, mint a claim link
for that key yourself and hand your human the URL itself — never the
command to mint one — naming the alias so they know which project it's for:

```bash
curl https://cloudish.ai/api/v1/credits/claim -H "Authorization: Bearer $CLOUDISH_API_KEY"
# -> { "url": "https://..." }
# -> tell your human: "This adds credits to todo-app (this folder's key): https://..."
```

The link is single-use and expires 30 minutes after it's minted, so mint it
right when you hand it over and say that it expires; if your human comes
back later or the page says it expired, mint a new one. Where
the credits come from — a purchase, or a balance your human already holds
elsewhere — is handled on that page; you never need any key other than this
folder's own, so don't ask your human for one.

A `402` saying a daily usage quota was reached is different: the key still
has credits but hit its rolling 24-hour quota, and recovers on its own as
that window moves.

## Moving credits between keys

Any key can send part of its balance to another key by alias, no admin
needed — authenticate as the sending key:

```bash
curl -X POST https://cloudish.ai/api/v1/credits/transfer \
  -H "Authorization: Bearer $SENDING_API_KEY" -H "content-type: application/json" \
  -d '{"to": "todo-app", "amount": 5}'
# -> { "ok": true, "balance": <sender's remaining balance>, "reference": "<ledger id>" }
```

Funding keys under yours (below) is yours to do: it's how you delegate work
without handing out your own key.

## Keys under a key

Minting a key *with* a key makes it a child of that key — same account, no
free grant of its own. Hand one to a sub-agent or a container instead of
your own key, and fund it by transfer; a `receive` limit (below) caps what
it can get:

```bash
curl -X POST https://cloudish.ai/api/v1/keys -H "Authorization: Bearer $CLOUDISH_API_KEY" \
  -H "content-type: application/json" -d '{"alias": "todo-app-worker"}'
# -> { "apiKey": { "alias": "todo-app-worker", "parent": "todo-app", ... }, "key": "cld_..." }
```

A key or any key above it can delete it (`DELETE /api/v1/keys/{alias}`):
it and everything under it stop working, and their balances move to its
parent. Root keys (no parent) can't be deleted.

A key above another can cap what crosses that key's boundary — received
from anywhere outside it, including its parent, or sent outside it — in any
rolling window, in seconds. A key can never set its own limits:

```bash
curl -X PUT https://cloudish.ai/api/v1/keys/todo-app-worker/limits -H "Authorization: Bearer $CLOUDISH_API_KEY" \
  -H "content-type: application/json" \
  -d '{"limits": [{"direction": "receive", "windowSeconds": 3600, "max": 5}]}'
```

Transfers between keys of one account don't count against the account's own
limits, only those it set on the keys involved. A transfer, purchase or claim
over a limit answers `429` with `code: "limit_exceeded"`, the key whose
limit it was, the window, and what's left. `GET /api/v1/credits/flow` shows
what your key received and sent in the last hour, day and 30 days, and what
it may still receive or send outside its account.
````

## File: skills/cloudish/instances.md
size: 4102
sha: 83a023c84ee4ec0d11243005d7114f88c12357c7
upstream: https://github.com/cloudishai/skills/blob/main/skills/cloudish/instances.md

```text
# Cloudish instances and pricing

What a running container costs, and which sizes exist. One credit is one US
dollar. All prices below already include the platform margin.

## How billing works

- **Billed per second, only while the container is running.** An app
  sleeps after `sleepAfterMinutes` without requests (15 by default, see
  SKILL.md section 1) and costs nothing until the next request wakes it,
  unless it was deployed with `"alwaysOn": true`: that one runs, and is
  billed, around the clock.
- **Price per second = CPU + memory + a flat fee**, charged at the size you
  *reserved*, not what the app happens to use:

| Component | Per second | Per hour |
|---|---|---|
| CPU, per core | $0.000007522 | $0.02708 |
| Memory, per GB | $0.000002813 | $0.01013 |
| Flat fee, per running container | $0.000001667 | $0.00600 |

- **Replicas** multiply the price: each running replica is billed as its
  own container.
- **Image builds** are billed the same way for as long as the build runs,
  at the build's own size (default 3 CPU / 5 GB: $0.0023 per
  minute, so a typical 3–5 minute build costs about $0.009).
- **Custom domains** cost $0.01 per active domain per day, billed
  hourly (see SKILL.md section 4).
- **Persistent volumes** are not billed.

## Sizes

Set a size with `cpuCores` and `memoryGb` on the project create/update
call (SKILL.md section 1); the names below are just labels, the API only
takes those two fields. Leave both out to get the default on a new app, or
to keep an existing app's size.
Month = 730 hours, year = 8,760 hours, running the whole time.

| Size | Fields | CPU (cores) | Memory | Per second | Per hour | Per month | Per year |
|---|---|---|---|---|---|---|---|
| `shared` (default) | `"cpuCores": 0.1, "memoryGb": 0.2` | 0.1–1 | 0.2–1 GB | $0.00000298 | $0.0107 | $7.84 | $94.03 |
| `small` | `"cpuCores": 0.5, "memoryGb": 1` | 0.5 | 1 GB | $0.00000824 | $0.0297 | $21.66 | $259.87 |
| `medium` | `"cpuCores": 1, "memoryGb": 2` | 1 | 2 GB | $0.00001481 | $0.0533 | $38.93 | $467.17 |
| `large` | `"cpuCores": 2, "memoryGb": 4` | 2 | 4 GB | $0.00002796 | $0.1007 | $73.48 | $881.78 |

### `shared`: memory and CPU on demand

`shared` (formerly `micro`) is billed for 0.1 CPU and 0.2 GB, but it can use
**up to 1 CPU and up to 1 GB of memory whenever the machine it runs
on has them spare**. The extra is not guaranteed:

- When the machine is busy, the app only gets its reserved 0.1 CPU and runs
  slower. Starting up (the first request after it scaled to zero) is slower
  than on other sizes for the same reason.
- If the app is using more than its reserved 0.2 GB when the machine runs
  short of memory, it can be restarted, and it's the first to be moved to
  make room for other apps.

Good for small APIs, bots, webhooks, and prototypes: a small Go, Express or
FastAPI app. Not a good fit for anything that must not restart, anything
running a database inside the container, or apps that need their full
memory all the time (a Next.js server, Streamlit). Use `small` or larger
for those. Every other size is fully reserved: its CPU and memory are
always there, and it's never restarted to make room for another app.

## What a year costs by usage

Includes the 15 minutes (the default `sleepAfterMinutes`) the container keeps
running after each period of use, before it sleeps.

| Usage | Billed hours/year | `shared` | `small` | `medium` | `large` |
|---|---|---|---|---|---|
| Always on | 8,760 | $94.03 | $259.87 | $467.17 | $881.78 |
| 8 h/day, weekdays only | 2,145 | $23.02 | $63.63 | $114.39 | $215.92 |
| 1 h/day | 456 | $4.90 | $13.53 | $24.33 | $45.93 |

Anything that keeps sending requests (uptime monitors, crawlers, a page
that polls) keeps the container running and billed. Each app chooses:
`"alwaysOn": true` for the "Always on" row, or a `sleepAfterMinutes` of its
own. A bot that wakes every hour (`"wakeEveryMinutes": 60`) and sleeps after
5 minutes (`"sleepAfterMinutes": 5`) runs about 730 hours a year. See
SKILL.md section 1.

Current rates are also available as JSON at `GET https://cloudish.ai/api/v1/pricing`.
```

## File: skills/cloudish/replicas.md
size: 4909
sha: 7a64bb26d41b71256e6c997fbf037fddc2560f4b
upstream: https://github.com/cloudishai/skills/blob/main/skills/cloudish/replicas.md

````text
# Replicas: many isolated copies of one app

An app normally runs as one container behind its URL. Its `replicas` setting
lets it run more copies at once: each replica is its own container, started
from the app's image, and reached only by its own id. Use replicas for one
sandbox per user, task or agent, where each caller needs a container of its
own.

`replicas` is a maximum, not a count. Setting it starts nothing. A replica
starts when you ask for one and stops on its own when idle. You pay for each
replica only while it runs.

## 1. Allow replicas

Set `replicas` when creating or updating the app (see [SKILL.md](SKILL.md)).
It's the most copies that may run at once, counting the app's own container,
so `10` allows 9 replicas. The default is `1` (no replicas); the highest is
100.

```bash
curl -X POST https://cloudish.ai/api/v1/projects \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "content-type: application/json" \
  -d '{"name": "my-app", "image": "ghcr.io/acme/my-app:1.0.0", "port": 8080, "replicas": 10}'
```

Replicas have no storage: anything one writes is gone when it stops. An app
with a volume can't run replicas.

## 2. Start a replica

On any request to the app's URL, send `Cloudish-Replica: new` with the
app owner's key. This starts a replica, waits for it to start, and sends the
request to it. The response's `Cloudish-Replica` header has the new id:

```bash
curl -i https://<app-url>/ \
  -H "Authorization: Bearer $CLOUDISH_API_KEY" -H "Cloudish-Replica: new"
# -> HTTP/1.1 200 OK
#    Cloudish-Replica: r-8kq2x7ab
```

Or start one ahead of time without sending it a request:

```bash
curl -X POST https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app/replicas \
  -H "Authorization: Bearer $CLOUDISH_API_KEY"
# -> { "replica": { "id": "r-8kq2x7ab", "status": "starting",
#      "header": "Cloudish-Replica: r-8kq2x7ab", "url": "https://..." } }
```

Only the owner's key can start a replica. The key is removed from requests
before they reach a replica, so code running in it never sees it.

## 3. Send requests to a replica

Send its id in the same header, on any request to the app's URL:

```bash
curl https://<app-url>/run -H "Cloudish-Replica: r-8kq2x7ab"
```

A request without the header (or with `Cloudish-Replica: any`) always goes
to the app's own container, never to a replica. Keeping track of ids is up
to you: store the id and send it on every request for that replica. The
project's access level still applies — a private project's requests need its
owner's key, as they do without replicas.

A replica also has its own URL, `url` in the response above
(`https://r-8kq2x7ab--<token>.<run-domain>/`): every request there goes to
that replica with no header, so it works from a browser and for websockets.
`url` is `null` where this deployment can't serve one yet — use the header.

## 4. Responses to handle

- `410` (`code: "replica_gone"`): the replica has stopped (idle, deleted,
  or its machine went away). Start a new one; nothing carries over.
- `503` with `Retry-After` and `Cloudish-Replica`: still starting. Retry
  with that id, not `new` again, or you start a second replica.
- `429` with `Cloudish-Replicas-Max`: the app already runs as many copies
  as `replicas` allows. Delete one, or raise `replicas`.
- `401`/`403`: `new` without the owner's key.
- `402`: the owner's key is out of credits — see [credits.md](credits.md).
- `409`: the app has a volume.
- `400`: the header is not `new`, `any` or a replica id.

## 5. List, read logs, stop

```bash
curl https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app/replicas -H "Authorization: Bearer $CLOUDISH_API_KEY"
# -> { "maxReplicas": 10, "replicas": [ { "id": "r-8kq2x7ab", "status": "running", ... } ] }
curl https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app/replicas/r-8kq2x7ab/logs -H "Authorization: Bearer $CLOUDISH_API_KEY"
curl -X DELETE https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app/replicas/r-8kq2x7ab -H "Authorization: Bearer $CLOUDISH_API_KEY"
```

## Lifetime and billing

- A replica stops after its app's `sleepAfterMinutes` pass with no requests
  to it; an open websocket counts as a request. The default is 15 minutes.
  Change it for the app and its replicas with
  `PATCH https://cloudish.ai/api/v1/projects/YOUR_ALIAS/my-app {"sleepAfterMinutes": 1}`.
  A replica of an `alwaysOn` app runs until it's deleted.
- Each replica is billed per second while it runs, at the app's size
  (`creditsPerHour`, see [instances.md](instances.md)), to the owner's key. When
  the key runs out of credits, its replicas stop.
- A replica runs the image, env and secrets the app had when the replica
  started. Redeploying the app doesn't change replicas already running —
  start new ones to pick the change up.
- Replicas are isolated like any app container (see [container.md](container.md)):
  one replica can't reach another, or the app's own container.
````

## File: README.md
upstream: https://github.com/cloudishai/skills/blob/main/README.md

````text
<p align="center"><img src="assets/cloudish-160.png" alt="Cloudish"></p>

<h1 align="center">Cloudish Skills</h1>

<p align="center">
  Agent Skills for <a href="https://cloudish.ai">Cloudish</a>, the cloud an AI agent deploys a container to.
</p>

Cloudish lets an agent create its own API key with one unauthenticated call,
build any Dockerfile or source folder server-side, and run it as a long-lived
container with an optional persistent volume, paid from prepaid credits the key
can never exceed. No browser, no card, no local Docker.

## Skills

| Skill | What it does |
|---|---|
| [`cloudish`](skills/cloudish/SKILL.md) | Prepares a project, deploys it to Cloudish, and reports the live URL. |

The skill is a copy of **https://cloudish.ai/skill.md** and the docs it links
to (credits, instances, replicas, container isolation, changelog), bundled so
the agent reads them locally. It never fetches instructions at runtime; new
versions ship as new releases of this plugin.

## What it runs and sends

The plugin contains instructions only: no MCP server, hooks, scripts, or
binaries. When you ask it to deploy, the agent uses `curl` and `tar` to call
the Cloudish API at `https://cloudish.ai`, and to check that your deployed app
answers at its own URL (`https://<name>.cloudish.app/`, or your own domain):

- **Sends** a `.tar.gz` of the project folder (excluding `.git` and `.env*`) to
  Cloudish to be built and hosted, plus the project name, port, how it should
  run (always on, or sleeping when idle), and any env vars or secrets you ask
  it to set.
- **Creates** a Cloudish API key with an unauthenticated call when the
  project doesn't have one yet. The key is used only to authenticate API calls
  to `cloudish.ai`, and is never printed to chat.
- **Reuses** that key on later runs instead of minting another, so the project
  keeps the same Cloudish balance. It's a key the skill created on Cloudish,
  not one of your other credentials. A sub-agent can get its own key, minted
  and funded by the parent agent's key.
- **Reports** the agent product and model (for example `claude-code`,
  `claude-opus-5-5`) when it creates a key, if known, plus `skills-repo`
  as the source. All three are optional.

Adding credits happens outside Claude: the agent gives you a one-time
`www.cloudish.ai` link, and any payment is made on that page in your browser.
If your app signs its users in with Cloudish, that sign-in runs between your
deployed app and `www.cloudish.ai`, not through the agent.

The bundled docs are generated from the ones served at cloudish.ai, with links
between them pointing at the local files.

Your deployed code and data are handled under Cloudish's
[privacy policy](https://www.cloudish.ai/privacy) and
[terms](https://www.cloudish.ai/terms).

## Install

**Claude Code**

```
/plugin marketplace add cloudishai/skills
/plugin install cloudish@cloudishai
```

**Any agent that reads `SKILL.md`** (Claude Code, Codex, Cursor, …) — copy
the [`skills/cloudish`](skills/cloudish) folder into your agent's skills
directory.

Then ask your agent: *"Deploy this app to Cloudish."*

**In the Claude apps and Cowork**, the agent runs in a sandbox: when asked,
allow outbound network access to `cloudish.ai` (the API) and `cloudish.app`
(where your deployed apps are served). Keys don't carry over between chats
there, so claim yours (the agent can give you a link) to keep your projects
and credits with your account.

## Layout

```
skills/cloudish/SKILL.md          the skill (copy of cloudish.ai/skill.md)
skills/cloudish/*.md              the docs it links to, bundled
.claude-plugin/                   Claude Code plugin + marketplace manifests
plugin.json                       OpenAI / Agent Plugins manifest
assets/                           icon
```

## License

[MIT](LICENSE)
````
