# Docker Compose (/docs/deploy/vm)



One Tenant, started with `nylorun start`. Compose runs the Runtime and its
stores. There are no replicas.

```sh
npx nylorun start --tenant app --no-open
```

## Provision [#provision]

Install Node 24 and Docker Compose v2. A running Tenant uses about 1.2 GB,
most of it Restate.

```sh
npx nylorun start --tenant app --no-open
npx nylorun status
```

`--no-open` skips the browser. Print a Studio login later with
`npx nylorun studio --no-open`.

## Reverse proxy [#reverse-proxy]

`nylorun start` publishes the Runtime on `127.0.0.1` only. Put a TLS reverse
proxy on the same machine:

| Rule                                           | Why                                                                        |
| ---------------------------------------------- | -------------------------------------------------------------------------- |
| Listen with TLS; forward to `127.0.0.1:<port>` | The application key travels on every request.                              |
| Rewrite `Host` to `localhost:<port>`           | The Runtime answers `421` to any other `Host`.                             |
| Forward only `/health`, `/ready`, and `/v1/*`  | Nothing else is the Tenant API.                                            |
| Never proxy the Admin API, Studio, or Restate  | Admin is on its own port (`8788`). Answer `/v1/admin/*` with `403` anyway. |
| Do not buffer responses                        | Event streams stay open with a keepalive every 15 seconds.                 |
| Pass `Authorization` and `Nylorun-*` through   | Your app sets those headers.                                               |

```text
runtime.example.com {
	@admin path /v1/admin /v1/admin/*
	handle @admin {
		respond "Blocked by the reverse proxy" 403
	}

	@api path /health /ready /v1/*
	handle @api {
		reverse_proxy 127.0.0.1:8787 {
			header_up Host localhost:8787
			flush_interval -1
		}
	}

	handle {
		respond 404
	}
}
```

| Placement | Certificate                                                                                   |
| --------- | --------------------------------------------------------------------------------------------- |
| Same LAN  | Tailscale, or a local CA (`tls internal`) whose root the app server trusts. Never plain HTTP. |
| Internet  | A public certificate. Prefer a private path (VPN or the same network) over a public endpoint. |

Firewall so only the proxy port and your SSH port are reachable.

## App server [#app-server]

On the app machine:

```sh
eval "$(npx @nylorun/cli env)"
```

| Variable              | Value                                                       |
| --------------------- | ----------------------------------------------------------- |
| `NYLORUN_RUNTIME_URL` | The proxy URL, `https://runtime.example.com`.               |
| `NYLORUN_SERVER_KEY`  | The application key `nylo env` prints. Never the admin key. |
| `NYLORUN_ACTIONS_URL` | A URL the Runtime's machine can reach.                      |

Register the Action endpoint at that URL. See
[Action endpoints](/docs/run/action-endpoints) and
[Serve your users](/docs/build#serve-your-users).

## Operate [#operate]

| Service    | Role                                       |
| ---------- | ------------------------------------------ |
| `runtime`  | Tenant API and scheduling.                 |
| `gateway`  | Model and tool calls. Holds provider keys. |
| `harness`  | Turns, stdio MCP, and workspaces.          |
| `studio`   | Operator dashboard on loopback.            |
| `postgres` | Session record.                            |
| `restate`  | Session advancement.                       |
| `s2-lite`  | Event history.                             |
| `rustfs`   | File bytes.                                |

```sh
npx nylorun status
npx nylorun logs runtime -f
curl -sS http://127.0.0.1:8787/health
curl -sS http://127.0.0.1:8787/ready
```

`GET /health` reports version, protocol range, and features. `GET /ready`
checks the listener, Tenant, Postgres, Restate, and s2; it answers `503`
until they are ready.

The Admin API listens on `127.0.0.1:8788` (`GET /v1/admin/status`,
`GET /v1/admin/openapi.json`). Leave it on the machine.

## Backups [#backups]

Back up the Postgres and RustFS volumes together with `keys/vault-kek`. Stored
credentials cannot be read without the vault key.

`nylorun stop` keeps volumes. `nylorun reset` deletes this Tenant's data.

## Upgrades [#upgrades]

Upgrade `nylorun` and run `start`. It refuses to run an older Runtime than the
Tenant last ran (`--allow-downgrade` overrides).

## Your own Postgres [#your-own-postgres]

Needs `wal_level=logical`, at least two replication slots, and a role with
`REPLICATION` plus read access to `nylorun_streams`. A local Tenant starts
Postgres with those settings already.

## Next step [#next-step]

<Cards>
  <Card title="Kubernetes" description="Run pod sandboxes on a cluster." href="/docs/deploy/kubernetes" />

  <Card title="Nylorun Cloud" description="A managed Tenant, by invitation." href="/docs/deploy/cloud" />
</Cards>
