---
name: uncloud
description: Operate the uncloud cloud platform from the terminal — sign in, deploy apps and monorepos, attach custom domains, set environment and scale, provision databases, read connection strings into .env, watch rollouts, and reach the rest of the control plane (droplets, volumes, buckets, pipelines, IAM) through the CLI or its REST API. Use when the user asks to deploy their app, ship to production, deploy all apps in a monorepo, point a domain at an app, add or attach a database, get database credentials or a DATABASE_URL, check what is deployed, sign in to uncloud, or manage uncloud infrastructure.
---

# Uncloud

`uncloud` is one binary over the control plane's REST API. Everything the
console can do, the CLI can do; anything it does not model is one
`uncloud api` call away.

## Before anything else

```bash
command -v uncloud || echo "not installed"
```

- **Not installed** → `npm i -g uncloud-cli` (or
  `curl -fsSL https://uncloud-cli.vercel.app/install.sh | sh`)
- **`not logged in`, or a 401 from any command** → tell the user to run
  `uncloud login`. It opens a browser, they approve once, and the token lands
  in `~/.uncloud/config`. It needs a human — do not try to script around it.
- **Headless (CI, cron, a sandbox with no browser)** → `UNCLOUD_API_URL` and
  `UNCLOUD_API_TOKEN`. Mint the token with `uncloud tokens create name=ci`.
- **Self-hosted control plane** → `uncloud login -api https://api.example.com`
- **`is out of date` and exit 6** → the CLI checks npm before every command and
  refuses to run when a newer release exists. Run the upgrade line it prints
  (it names the right one for how this copy was installed), then retry. Do not
  reach for `UNCLOUD_SKIP_UPDATE_CHECK=1` to get past it — an old CLI against a
  newer control plane reports errors that have nothing to do with the real
  problem, which costs far more time than the upgrade. That escape hatch is for
  CI that pins a version deliberately.

## Global flags

`-json` for machine-readable output, `-y` to confirm a destructive command,
`-app NAME` to target an app from anywhere, `-project ID` to scope the request.
They can appear anywhere in the line.

**`-json` is how you read results.** Tables are for humans; parse the JSON.

```bash
uncloud -json apps list | jq -r '.apps[] | "\(.name) \(.replicas)"'
```

## Deploying

The first deploy in a directory creates the app and writes
`.uncloud/config.json`. Later commands in that directory need no arguments.

```bash
uncloud deploy                 # create on first run, then build
uncloud deploy -name api       # name it explicitly the first time
uncloud redeploy               # roll out the current spec, no rebuild
```

`uncloud status` refreshes until Ctrl-C — **never run it in a non-interactive
context.** Use `uncloud apps status APP_ID` for a single reading.

### Monorepos

Folders holding `.uncloud/config.json` are already-known apps. Otherwise write
`.uncloud-apps` at the repo root, one path per line, then:

```bash
uncloud deploy all
```

## Everyday operations

```bash
uncloud logs                          # container logs
uncloud env                           # print the environment
uncloud env KEY=value OTHER=thing     # merge and save
uncloud env STALE_KEY-                # unset with a trailing dash
uncloud scale 3                       # replicas, and roll out
uncloud open                          # print the public URL
```

`env` changes are saved immediately but reach the container on the next
`uncloud redeploy`. Say so rather than leaving the user to wonder.

## Domains

```bash
uncloud domain add blog.example.com
```

The command prints the exact DNS record to create. Nothing serves on that
hostname until the record exists, and the certificate is issued on the first
rollout **after** DNS resolves — so the order is: add, create the record,
confirm with `dig`, then `uncloud redeploy`.

## Databases

```bash
uncloud db add                        # Postgres for this app; dev by default
uncloud db add -workload production   # 3 replicas + connection pooler
uncloud db env                        # reprint, create nothing
```

Output is `.env`-shaped. **Append it to the user's `.env`; never overwrite
one**, and never paste real credentials into a commit, a PR body, or chat
output the user did not ask for.

Other engines go through the resource layer:

```bash
uncloud databases create name=cache db_type=redis version=7 \
  plan_id=s-1vcpu-1gb region=nyc1 storage_gb=10 replicas=1
```

`db_type` is one of `postgresql`, `mysql`, `redis`, `mongodb`.

## Everything else

Every resource takes the same verbs — `list`, `get ID`, `create key=value…`,
`update ID key=value…`, `delete ID` — plus its own:

`apps` `databases` `droplets` `volumes` `buckets` `clusters` `pipelines`
`backups` `webhooks` `tokens` `ssh-keys` `projects` `users` `roles` `groups`
`cronjobs` `templates` `loadbalancers` `security-groups` `plans` `regions`
`images` `nodes` `events` `audit`

Run a resource with no verb to see what it supports:

```bash
uncloud droplets
uncloud apps deploy app_08754a47
uncloud droplets actions dpl_abc123 type=reboot
```

Values are typed: `replicas=2` is a number, `active=true` a boolean,
`env.DB_HOST=db:5432` nests, `@body.json` and `-` read a whole body.

### The escape hatch

```bash
uncloud api GET /api/v1/dashboard/stats
uncloud api PATCH /api/v1/apps/app_x1 replicas=3
```

Any method, any path, same credentials. Use it for endpoints with no command
yet rather than telling the user something is impossible.

## Rules that matter

**Destructive calls need a human.** `delete` prompts, and refuses outright when
there is no terminal to prompt. Do not reach for `-y` to get past that on your
own initiative — say what would be removed and get an explicit yes. Deleting an
app, a database, or a volume is not recoverable.

**`uncloud api DELETE` does not prompt at all.** It sends exactly what you ask.

**Do not invent credentials.** There is no token flag; sign-in writes the
config. If a command says not logged in, the fix is always `uncloud login` by
the user.

## When something fails

| Symptom | Cause | Fix |
| --- | --- | --- |
| `not logged in` | No token on this machine | User runs `uncloud login` |
| 401 on every command | Token revoked or expired | `uncloud login` again |
| 403 with a valid token | Token scoped to another project | Check `-project` |
| `no app in this directory` | Never deployed here | `uncloud deploy`, or pass `-app` |
| `no app named "x"` | Name/slug mismatch | `uncloud apps list` |
| `no database attached` | No database for this app | `uncloud db add` |
| `Refusing to delete without confirmation` | No terminal to prompt | Confirm with the user, then `-y` |
| Custom domain 404s or has no TLS | DNS not pointing at the ingress yet | `dig` the name, then `uncloud redeploy` |
| Connection refused | Wrong control plane | `UNCLOUD_API_URL`, or `uncloud login -api …` |

Full reference: https://docs.uncloud.club/cli/reference
