# Apps

> Deploy from a source directory or a prebuilt image. Rolling deploys, secrets, logs, releases and rollbacks.

An app is a name with an address. It runs one or more machines from an image you deploy, or, for a static site, no machine at all. Machines, deploys, secrets, logs and releases all belong to an app.

## Names and addresses

App names are unique across all of Rytiv: 2 to 63 characters, lowercase letters, digits and hyphens, starting with a letter. A few names are reserved for the platform (for example `api`, `console`, `docs` and `www`, and every name that is `rytiv` or starts with `rytiv-`).

An app answers on `https://<app>.rytiv.app`. Extra ports of a machine answer on `https://<port>-<app>.rytiv.app`.

## Deploy from source

Point the CLI at a directory. Rytiv reads the project, builds it on its own machines and deploys the result:

```bash
ry deploy web --source .
```

Or run `ry` alone in the directory: it signs you in if needed, shows what it detected, asks for an app name and deploys.

```bash
ry
```

**What is detected.** Node.js projects with these frameworks: Next.js, SvelteKit, Nuxt, Remix, TanStack Start, SolidStart, Analog, Astro, Vite, Gatsby, Docusaurus, Create React App, VitePress, Eleventy and Angular, plus plain Node.js. A `Dockerfile` or `Containerfile` at the root of the directory wins over the detection; anything that is not Node.js needs one. `ry detect` shows the finding without changing anything.

**Static sites** (for example Astro without an adapter, Next.js with `output: "export"`, Vite, or a folder with an `index.html`) need no machine: the built files are served directly.

### rytiv.json

`ry deploy` reads a `rytiv.json` in the current directory or above it, so a deploy needs no flags. `ry config init <app>` writes one. A flag on the command line always wins over the file.

```json
{
  "app": "web",
  "source": ".",
  "port": 3000,
  "memoryMb": 512,
  "vcpus": 1
}
```

Other keys: `image` (instead of `source`), `diskMb`, `buildMemoryMb`, `count`, `start`, `healthPath`, `healthTimeoutS`, `restart`, `killSignal`, `killTimeoutS`, `sleep` and `env`. An unknown key is an error.

## Deploy an image

**CLI**

```bash
ry deploy web --image nginx:alpine --port 80
```

**curl**

```bash
curl -X POST https://api.rytiv.app/v1/apps \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "web"}'

curl -X POST https://api.rytiv.app/v1/apps/web/deploy \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image": "nginx:alpine", "internalPort": 80}'
```

**TypeScript**

```ts
const api = "https://api.rytiv.app/v1";
const headers = {
  Authorization: `Bearer ${process.env.RYTIV_TOKEN}`,
  "Content-Type": "application/json",
};

await fetch(`${api}/apps`, { method: "POST", headers, body: JSON.stringify({ name: "web" }) });
const res = await fetch(`${api}/apps/web/deploy`, {
  method: "POST",
  headers,
  body: JSON.stringify({ image: "nginx:alpine", internalPort: 80 }),
});
const deploy = await res.json(); // { id, status: "running", ... }
```

`POST /v1/apps` answers `201`, or `409` with `name-taken` if the name is used anywhere on Rytiv. `POST /v1/apps/:name/deploy` answers `202` with the deploy. Poll `GET /v1/deploys/:id` until `status` is `done` or `error`. On `error`, the answer names the reason in `error` and carries the last lines of the machine's console in `bootLog`.

### Deploy request

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `image` | string | required | An OCI reference such as `nginx:alpine`, or `build:<id>` for a build of yours |
| `internalPort` | integer | `3000` | The port your app listens on |
| `memoryMb` | integer | kept, `512` for a new app | 128 to 16384 |
| `vcpus` | integer | kept, `1` for a new app | 1 to 16 |
| `diskMb` | integer | kept | 512 to 131072 |
| `count` | integer | kept, `1` for a new app | Machines, 1 to 20 |
| `env` | object | `{}` | Plain environment variables. Use [secrets](#secrets) for credentials. |
| `start` | string | | Start command, run through `/bin/sh -c` |
| `healthCheck` | object | | `path` for an HTTP check (empty: the port only), `timeoutMs`, `intervalMs`, `healthyThreshold`, `maxWaitMs` |
| `restart` | object | | `policy`: `never` or `on-failure`, with `retries` 1 to 10. Without it, a crashed machine is restarted. |
| `killSignal` | string | the image's, else `SIGTERM` | Sent before a machine is stopped or replaced |
| `killTimeoutS` | integer | `30` | Seconds between that signal and `SIGKILL`, 1 to 300 |

"Kept" means: a deploy that does not name the field keeps what the app's machines have. Every other field comes from the request or its default, so send `internalPort`, `env` and `start` with every deploy.

### How a deploy rolls

A deploy starts a new machine, waits until it runs and answers on its port (and passes the health check, if you set one), then takes one old machine out of traffic and removes it, one at a time. If a new machine fails, the deploy stops, the new machine is discarded and the old version keeps serving.

An app with a volume works differently: its single machine stops before the new one starts, so there is a short gap.

### Sleep when idle

An app sleeps only if you give it a sleep rule. Without one, its machines run until you stop them. Name the idle time on a deploy:

```bash
ry deploy web --sleep 5m
```

A machine that has had no requests and no CPU load for that long goes to sleep, and the next request wakes it. Up to 2 GB of memory it sleeps with its memory and resumes in about a quarter of a second; a larger machine stops instead and boots cold, and its files stay. `--sleep off` takes the rule away again, for a worker or a cron app that has to stay awake. In a [deploy request](#deploy-request) the same rule is `suspendPolicy`, for example `{"mode": "suspend", "idleSeconds": 300}`.

## Secrets

Secrets are environment variables stored encrypted. Nobody can read them back, not even you: the API lists names, never values.

**CLI**

```bash
ry secrets set web API_KEY=example-value OTHER_KEY=other-value
```

**curl**

```bash
curl -X PUT https://api.rytiv.app/v1/apps/web/secrets \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"secrets": {"API_KEY": "example-value"}, "staged": true}'
```

| Method | Path | What it does |
| --- | --- | --- |
| `PUT` | `/v1/apps/:name/secrets` | `{secrets, staged}`. With `staged: true` (the default) the values apply at the next deploy. With `staged: false` Rytiv rolls the app now if a machine lacks the new value. |
| `GET` | `/v1/apps/:name/secrets` | `name`, `updatedAt` and `managed` for every secret. Never a value. |
| `DELETE` | `/v1/apps/:name/secrets/:key` | Removes one secret. `204`. |

Secrets that Rytiv manages for you, such as the `DATABASE_URL` of an [attached database](https://docs.rytiv.com/postgres.md#2-connect-from-an-app), cannot be changed or removed this way. Each secret carries a `build` choice: whether the next source build sees it. A new secret is marked for the build only when its name starts with a public prefix such as `NEXT_PUBLIC_` or `VITE_`; `ry secrets set <app> KEY --build` marks one, `--no-build` takes the mark away.

## Logs

```bash
ry logs web -f
```

`GET /v1/apps/:name/logs` answers `{app, machines}`, one entry per machine with the console output of that machine in `text` and a `cursor`. Pass `cursor.<machineId>=<cursor>` on the next call to get only what is new, and `machine=<id>` for one machine.

Logs are what your app's machines write to their console.

## Machines

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/apps/:name/machines` | The app's machines |
| `GET` | `/v1/machines/:id` | One machine |
| `POST` | `/v1/machines/:id/start` | Start a stopped or suspended machine |
| `POST` | `/v1/machines/:id/stop` | Stop it |
| `POST` | `/v1/machines/:id/suspend` | Suspend it |
| `POST` | `/v1/machines/:id/destroy` | Remove it |

A machine is `requested`, `scheduled`, `starting`, `running`, `suspending`, `suspended`, `stopping`, `stopped`, `deleted` or `failed`.

```bash
ry machines web
ry exec web -- ls -la /app
```

## Releases and rollback

Every deploy is a release. A rollback deploys an earlier release's image again, without a rebuild, with the app's current settings. Secrets, volumes and databases stay as they are.

```bash
ry releases web
ry rollback web
```

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/apps/:name/releases` | The releases, which one runs, and which one a rollback would bring back |
| `POST` | `/v1/apps/:name/rollback` | `{}` for the previous release that went live, or `{version}` or `{releaseId}`. Answers `202` with the deploy. |

## Delete an app

```bash
ry apps destroy web
```

`DELETE /v1/apps/:name` removes the app and all its machines and answers `204`. It cannot be undone. The CLI asks before it does this; pass `--yes` in scripts. An app that runs a [database](https://docs.rytiv.com/postgres.md) is removed with the database, not on its own.

## Errors you will meet

| Status | Code | When |
| --- | --- | --- |
| `400` | `name-reserved` | The name is reserved for the platform |
| `409` | `name-taken` | The name is used somewhere on Rytiv |
| `403` | `org-limit-apps` | An account on the starting credit can have 10 apps, computers included. See [Credit and pricing](https://docs.rytiv.com/billing.md#limits-on-the-starting-credit). |
| `402` | `credit-blocked` | The credit is used up: nothing new starts until a top-up. See [Credit and pricing](https://docs.rytiv.com/billing.md#when-the-credit-is-used-up). |
| `404` | `build-not-found` | `build:<id>` is not a build of your organization |
| `409` | `deploy-in-flight` | Another deploy of this app is running |
| `409` | `volume-needs-single-machine` | An app with a volume runs exactly one machine |

See the [CLI reference](https://docs.rytiv.com/reference/cli/deploy.md) for every flag of `ry deploy`, and [Apps](https://docs.rytiv.com/reference/api/apps.md) and [Deploys and releases](https://docs.rytiv.com/reference/api/deploys.md) in the API reference for every route with its fields. The guide [Deploy a Next.js app](https://docs.rytiv.com/guides/deploy-nextjs.md) walks the path from a directory to a URL.
