# Configuration

> The inf.yml file defines app settings and resource requirements.

**URL:** https://inference.sh/docs/extend/configuration
**Last updated:** 2026-10-15

---

The `inf.yml` file defines app settings and resource requirements.

---

## Project Structure

<div data-type="codegroup">

```text Python
my-app/
├── inf.yml           # Configuration
├── inference.py      # App logic
├── requirements.txt  # Python packages (pip)
└── packages.txt      # System packages (apt) — optional
```

```text Node.js
my-app/
├── inf.yml           # Configuration
├── src/
│   └── inference.js  # App logic
├── package.json      # Node.js packages (npm/pnpm)
└── packages.txt      # System packages (apt) — optional
```

</div>

---

## Basic structure

```yaml
namespace: myteam
name: my-app
title: My App
description: What my app does
category: image
kernel: python-3.11

resources:
  gpu:
    count: 1
    vram: 24    # 24GB (auto-converted to bytes)
    type: any
  ram: 32       # 32GB
```

---

## Fields

| Field | Required | Description |
|-------|----------|-------------|
| `namespace` | Yes | Your workspace username (app owner). Another namespace needs admin access; see [Deploying](/docs/extend/deploying#namespace) |
| `name` | Yes | App identifier (slug format). This is the slug that addresses the app: `namespace/name` in the API, the CLI and the URL. |
| `title` | No | The name a person reads on the app's page — `Veo 3.1` for `veo-3-1`. Free of the slug's constraints: spaces, capitals and punctuation are fine. Without one, the app is presented under its `name`. |
| `description` | Yes | What it does (max [200 characters](/docs/api/rest/overview#description-length-limits); `belt app deploy` warns when over the limit, including with `--dry-run`) |
| `category` | Yes | Grid grouping — see [Categories](#categories) |
| `kernel` | Yes | Runtime: `python-3.10`, `python-3.11`, `python-3.12`, `node-22` |
| `resources` | Yes | Hardware requirements |
| `images` | No | Store listing images (`card`, `thumbnail`, `banner` URLs; optional `dark` with the same slots) |

---

## App tags

Tags describe **what the app does** and **traits** such as `open-weights` or `realtime`. They appear on the app record, in [store listings](/docs/api/rest/store), and in search indexing alongside name and description.

Declare them under version `metadata` in `inf.yml`:

```yaml
metadata:
  tags:
    - text-to-image
    - open-weights
```

| Rule | Detail |
|------|--------|
| **Format** | Lowercase slugs: letters, digits, and single hyphens (`text-to-image`, `lora`). Up to **10** tags per app, each at most **50** characters. |
| **Browseable tags** | Tags on at least three public apps appear in `GET /store/tags` with a display title and count — see [Store API — List browseable tags](/docs/api/rest/store#list-browseable-tags). Other valid slugs (for example `virtual-try-on`, `ocr`) work on apps and in filters; they are omitted from that list until usage crosses the threshold. |
| **On deploy** | When the deploy request does not send `tags`, the API normalizes `metadata.tags` from the version and stores them on the app. Invalid entries in `inf.yml` are **dropped** (they do not fail deploy). If `metadata.tags` is empty too, the stored tags are kept. |
| **Explicit API tags** | `POST /apps` and deploy payloads may include a top-level `tags` array — it **replaces** the app's tags and takes precedence over `metadata.tags`. |

`belt app deploy --meta` updates listing fields such as `category` and `images`; app tags are set on full deploy (or via the REST API), not through `--meta` alone.

In the web app, open your app → **Info** to edit **category** and **tags**. The **tags** field is a list: press **Enter** or type a comma to add a slug (up to 10). A later deploy that sends `tags` or declares `metadata.tags` replaces tags set only in the UI.

---

## Store images

Optional URLs shown in the app Grid and store. The CLI reads these from `inf.yml` when you deploy:

```yaml
images:
  card: https://example.com/card.png
  thumbnail: https://example.com/thumb.png
  banner: https://example.com/banner.png
  dark:                       # optional: covers for the dark theme
    card: https://example.com/card-dark.png
    thumbnail: https://example.com/thumb-dark.png
    banner: https://example.com/banner-dark.png
```

Without `dark`, the three slots serve both themes. Omit `images` to use auto-generated covers after deploy.

To update the listing title, images, description, or category without shipping a new app version:

```bash
belt app deploy --meta
```

Only non-empty fields in your local `inf.yml` are sent (for example, omit `images.banner` to leave the existing banner unchanged). The app must already be deployed at least once before `--meta` works.

---

## App UI

Optional version metadata that names an [artifact](/docs/artifacts) page to show beside finished app tasks, in the web app, in chat, and in MCP clients that support MCP Apps (see [MCP server](/docs/connectors/mcp-server)).

```yaml
metadata:
  ui:
    artifact: myteam/run-dashboard        # artifact ref namespace/name
    # artifact: myteam/run-dashboard@3    # pin one published version
    # artifact: art_abc123                # or artifact id
```

| Value | Behavior |
|-------|----------|
| Ref without `@` | Follows the artifact's current published version when a task finishes |
| Ref with `@version` | Pins one published version |
| Artifact id | Resolves that artifact; unpinned versions follow the live page |

The artifact must exist and be readable by you at deploy time. The page must also be at least as visible as the app (a `public` app needs a `public` page), so everyone who can see the app can see its page. Otherwise deploy is refused with **400** `validation_error` naming the page. The API exposes the same object on `AppVersionDTO.ui` and in `metadata.ui`; see [Apps API — custom UI page](/docs/api/rest/apps#custom-ui-page-ui).

Publish the page first (`belt artifact publish`), then deploy the app with this block. See [Artifacts — capabilities](/docs/artifacts/capabilities) for what the HTML page may call (`db`, `user`, `assets`).

---

## Resources

The CLI automatically converts human-friendly values to bytes:
- **< 1000** → treated as GB (e.g., `80` = 80GB)
- **1000 to 1 billion** → treated as MB (e.g., `80000` = 80GB)

```yaml
resources:
  gpu:
    count: 1        # Number of GPUs
    vram: 24        # 24GB
    type: any       # GPU type
  ram: 32           # 32GB
```

### GPU Types

| Value | Description |
|-------|-------------|
| `any` | Any GPU will work |
| `nvidia` | Requires NVIDIA GPU |
| `amd` | Requires AMD GPU |
| `apple` | Requires Apple Silicon |
| `none` | No GPU needed (CPU only) |

> **Note:** Currently only NVIDIA CUDA GPUs are supported.

An app has one `resources` block. [Setup parameters](/docs/apps/setup-parameters) choose a model or configuration inside it, so the block has to fit the largest one. Models that need different amounts of memory are separate apps.

For CPU-only apps:
```yaml
resources:
  gpu:
    count: 0
    type: none
  ram: 4
```

---

## Categories

| Category | Use For |
|----------|---------|
| `image` | Image generation, editing |
| `video` | Video generation, processing |
| `audio` | Audio generation, TTS |
| `text` | Text generation |
| `chat` | Conversational AI |
| `3d` | 3D model generation |
| `flow` | Apps deployed from a [Flow](/docs/flows/overview) |
| `decision` | **Decision models**: typed questions in, a probability for every answer out (no generative output). See [Decision apps](/docs/concepts/apps#decision-apps) and the [authoring contract](/docs/extend/app-code#decision-models-contract) |
| `other` | Everything else |

---

## Dependencies

<div data-type="codegroup">

```text requirements.txt (Python)
torch>=2.0
transformers
accelerate
```

```json package.json (Node.js)
{
  "type": "module",
  "dependencies": {
    "zod": "^3.23.0",
    "sharp": "^0.33.0"
  }
}
```

</div>

### System Packages (packages.txt)

For apt-installable system dependencies (both Python and Node.js):

```
ffmpeg
libgl1-mesa-glx
```

---

## Base Images

Apps run in containers with these base images:

| Type | Image |
|------|-------|
| GPU | `docker.inference.sh/gpu:latest-cuda` |
| CPU | `docker.inference.sh/cpu:latest` |

### Python environment

Each Python app version gets its own venv. It starts with build tools (`setuptools`, `setuptools-scm`, `wheel`, `wheel-stub`, `build`, `packaging`, `ninja`, `scikit-build-core`, `cython`, `flit-core`, `numpy`) plus `pydantic` and `inferencesh`.

`torch` and its CUDA wheels, `accelerate`, `huggingface_hub`, `transformers` and `diffusers` are not preinstalled. List every package your code imports in `requirements.txt`. `transformers` and `diffusers` need `accelerate` at runtime for `device_map`, `low_cpu_mem_usage` and `enable_model_cpu_offload()`.

App versions that already have an environment keep it. New versions, and versions set up on a new worker, get this base.

---

## Environment Variables

```yaml
env:
  MODEL_NAME: gpt-4
  MAX_TOKENS: "2000"
  HF_HUB_ENABLE_HF_TRANSFER: "1"
```

Access in code:

<div data-type="codegroup">

```python Python
import os
model = os.environ["MODEL_NAME"]
```

```javascript Node.js
const model = process.env.MODEL_NAME;
```

</div>

---

## Secrets and Credentials

Declare required secrets and OAuth credentials:

```yaml
credentials:
  huggingface:
    secrets: [HF_TOKEN]
    description: HuggingFace token for gated models

  google:
    capabilities: [google.sheets]
    description: Access to Google Sheets
    optional: true
```

Entries are keyed by the credential slug. The older list form (`- provider: huggingface`, `- key: google.sheets`) is still read. Use `credentials:` only; `integrations:` in `inf.yml` is rejected (renamed in CLI v1.18.59).

`credentials:` is the app-wide list. In a multi-function app, each function can declare the subset it uses; see [Per-function credentials](/docs/extend/credentials#per-function-credentials).

See [Secrets](/docs/extend/secrets) and [Credentials](/docs/extend/credentials) for details.

---

## Next

→ [Deploying](/docs/extend/deploying)
