# Deploying

> Push your app to inference shell.

**URL:** https://inference.sh/docs/extend/deploying
**Last updated:** 2026-10-11

---

Push your app to inference shell.

---

## Deploy

```bash
belt app deploy
```

### Useful flags

| Flag | Description |
|------|-------------|
| `--dry-run` | Run validation, packaging, and upload checks without registering a new version |
| `--meta` | Update listing fields from `inf.yml` only (no new version): `title`, `description`, `category`, and non-empty `images` (`card`, `thumbnail`, `banner`) |
| `--stage` | Deploy as a staged version (not current) |

After `inf.yml` is loaded, deploy prints a **warning** (not a hard error) when `description` is longer than the [listing limit](/docs/api/rest/overview#description-length-limits) (200 characters). `belt app deploy --dry-run` shows the same warning; a dry run still builds and uploads the package and skips only registering the version (`POST /apps`). The API still rejects a **new or changed** description over the limit; an unchanged long description on an already-deployed app is kept until you edit the field.

### Namespace

An app deploys into the `namespace` from `inf.yml`.

| Namespace in `inf.yml` | Result |
|---|---|
| Empty, or your active workspace | Deploys into your workspace |
| Another namespace, and you are a member | Deploys into your own workspace (a copied `inf.yml` keeps its author's namespace) |
| Another namespace, and you are platform staff | Needs admin access. From the CLI, run `belt admin elevate` and approve it in the browser first; without it the deploy is refused with `Deploying into namespace "…" needs admin access` |

### Warnings

After the version is registered, the API can return warnings about the app's credentials and annotations. The CLI prints each one with `!` after the deployment summary. The deploy still succeeds.

| Code | When |
|---|---|
| `readonly_broad_credential` | A function marked `readOnlyHint: true` requires a capability that has a narrower read-only counterpart |
| `unused_credential` | Every function declares its own `credentials`, and an `inf.yml` entry is used by none of them |
| `missing_annotations` | The app uses credentials and a function sets neither `readOnlyHint` nor `destructiveHint` |

→ [Per-function credentials](/docs/extend/credentials#per-function-credentials)

### JSON output

`belt app deploy --json` prints one result object (one per app with `--all`):

```json
{
  "success": true,
  "namespace": "myteam",
  "app": "my-app",
  "version": "2df033wcb89a83z8wq3hw2mkm3",
  "short_id": "2df033wc",
  "warnings": [
    { "code": "missing_annotations", "function": "delete_file", "message": "delete_file has no annotations; clients will treat it as destructive" }
  ]
}
```

`warnings` is `[]` when there are none. `error` is present only when the deploy failed.

Use `--meta` after editing store listing fields in [Configuration](/docs/extend/configuration#store-images), for example `title`, `description`, `category`, or `images`. Only non-empty values in your local `inf.yml` are sent. The app must already be deployed once.

---

## What happens

1. **Validation** — Checks required files and runs `belt app doctor` diagnostics (errors block deploy; warnings are shown but do not stop the build)
2. **Credential keys** — For each `credentials:` entry with a `key`, the CLI checks that the capability exists (`belt app credentials list`). Provider-only entries (`provider` with `secrets`, no `key`) are not capabilities; the API validates those when the version is registered
3. **Dependencies** — Installs packages locally
4. **Packaging** — Bundles everything
5. **Upload** — Sends to inference shell
6. **Registration** — Makes it available

Run diagnostics manually before deploy:

```bash
belt app doctor          # list issues (exits 0 — report only)
belt app doctor fix      # auto-fix what it can (relative imports, missing __init__.py, GPU deps, ESM config, …)
belt app doctor --check   # exit 10 (config) when errors are found (CI gate)
```

Use `--json` with `--check` for machine-readable output in scripts. → [CLI setup — App health](/docs/extend/cli-setup#app-health-belt-app-doctor)

→ [CLI setup](/docs/extend/cli-setup)

---

## Output

```
┌──────────────────────────────────────┐
│         deployment summary           │
├──────────────────────────────────────┤
│                                      │
│ final status: success                │
│ duration: 45s                        │
│ app: my-app                          │
│ link: https://app.inference.sh/...   │
│                                      │
└──────────────────────────────────────┘
```

---

## View your app

After deploy:
- Find it in the Grid
- Run it from the workspace
- Add it to agents

---

## Update

Make changes, then deploy again:

```bash
belt app deploy
```

New version is created and becomes the active version unless you used `--stage`.

### Staged deploys and promotion

`belt app deploy --stage` uploads a new version without making it current. Tasks and the store listing keep using the previous current version until you promote the staged one.

When you are ready to go live:

- **Workspace** — open the app and set the active version under **Versions**
- **REST API** (requires `apps:write`):

```bash
curl -X PUT "https://api.inference.sh/apps/{appId}/versions/{versionId}/current" \
  -H "Authorization: Bearer inf_your_api_key"
```

Returns the updated app object. `appId` and `versionId` are server IDs (from `belt app my -l` or the deploy response), not `namespace/name` refs.

**JavaScript SDK:**

```typescript
await client.apps.setCurrentVersion('app_xyz', 'ver_abc');
```

The Python SDK does not yet expose this helper; use the REST call above.

Runs and agent tools resolve `namespace/name` without a version pin to the **current** version.

---

## List your apps

```bash
belt app my
belt app my -l  # Detailed list
```

---

## Next

If you want programmatic access:

→ [API Authentication](/docs/api/authentication)
