---
title: 'Self-host camelRun'
sidebarTitle: 'Self-hosting'
description: 'Run the camelRun runtime on your own host with Docker Compose and Postgres'
---
[View as Markdown](https://camelai.com/docs/camelrun/self-hosting.md) · [Docs index for coding agents](https://run.camelai.com/llms.txt)

camelRun runs on any host with Docker: one container, and Postgres. Nothing in
it needs AWS. Your application talks to it the same way it talks to
run.camelai.com: point the SDKs and the CLI at it with `CAMELAI_BASE_URL` (or `url`).

## Licensing

The runtime is licensed under the GNU AGPL-3.0 ([LICENSE](https://github.com/qaml-ai/run/blob/main/LICENSE)). The SDKs, frontend packages,
CLI and examples are MIT, so the code you write against camelRun can use them in
any application. If the AGPL-3.0 doesn't work for your organization, commercial
licenses for the runtime are available: contact us through
[camelai.com](https://camelai.com).

## What you need

* Docker Engine with Compose v2, on `linux/amd64` or `linux/arm64`.
* 1 vCPU and 2 GB of memory for the runtime, which is enough for tens of agents
  awake at once. Agents asleep cost only storage.
* Postgres 14 or later.
* Outbound HTTPS to your model providers.

The runtime doesn't need to be public. Your application can reach it on a
private network, and browsers through your application (see
[Networking](#networking)).

The image is `ghcr.io/qaml-ai/run:<version>` (for example `0.1.0`,
or `latest`), built for `linux/amd64` and `linux/arm64`. The container needs
no extra privileges: no `--privileged`, no added capabilities, no Docker socket. It runs each sandbox process for model-written
code as its own user under a seccomp filter. A seccomp or AppArmor profile
stricter than Docker's default can stop that, and the runtime then refuses to
start rather than run code unsandboxed.

## Start it

[`deploy/selfhost/`](https://github.com/qaml-ai/run/tree/main/deploy/selfhost)
in the repository has a Compose file and an example environment:

### Configure

```bash
git clone https://github.com/qaml-ai/run
cd run/deploy/selfhost
cp .env.example .env    # fill it in; generate secrets with: openssl rand -hex 32
# or fill every empty secret at once:
for v in AGENT_OPERATOR_TOKEN AGENT_SESSION_SECRET AGENT_SECRETS_KEY POSTGRES_PASSWORD; do
  sed -i.bak "s/^$v=$/$v=$(openssl rand -hex 32)/" .env; done; rm .env.bak
```

Set at least `AGENT_OPERATOR_TOKEN`, `AGENT_SESSION_SECRET`,
`AGENT_SECRETS_KEY` and `POSTGRES_PASSWORD`. The example defaults to a
private runtime at `http://runtime:8790` on the Compose network. See
[Configuration](/docs/camelrun/configuration).

### Start the runtime

```bash
docker compose up -d
curl -s localhost:8790/healthz
```

The runtime listens on `127.0.0.1:8790` on the host. `AGENT_RUNTIME_PORT`
and `AGENT_RUNTIME_BIND` in `.env` change the port and the address
(`0.0.0.0` for every interface). The Compose project is
`COMPOSE_PROJECT_NAME` in `.env` (`agent-runtime`), and its volumes are
named after it. To run a second copy beside another stack of that name,
give it its own name there, the same one every time: a different name
starts with empty volumes.

### Check your token

The operator token from `.env` is your API key:

```bash
curl -s localhost:8790/v1/me -H "Authorization: Bearer $AGENT_OPERATOR_TOKEN"
```

Agents can be made before any model key is set (image `0.2.0` and later;
earlier images refuse to make one with `400 INVALID_REQUEST` until a key
is set). Their runs fail with `model_key_missing`, saying which key to
set, until one is: set `AGENT_TENANT_API_KEYS` (the error names it too),
or `PUT /v1/providers/{provider}/key` (add `"verify": false` to store a
key without checking it with the provider).

### Point your application at it

The SDKs, the CLI and `npm create @camelai/run-app` read the runtime's URL
from `CAMELAI_BASE_URL` and the key from `CAMELAI_API_KEY`. On a
self-hosted runtime the key is the operator token:

```bash
export CAMELAI_BASE_URL=http://localhost:8790   # http://runtime:8790 from the Compose network
export CAMELAI_API_KEY="$AGENT_OPERATOR_TOKEN"
npx -y @camelai/camelrun whoami                 # the tenant, and the default model
```

Everything in the [Quickstart](/docs/camelrun/quickstart) then works against it
unchanged. In code, `new Agents({ url, apiKey })` (Python
`Agents(url=..., api_key=...)`) does the same.

## Upgrade

The Compose file runs a pinned release, `ghcr.io/qaml-ai/run:0.2.0`. Each
release is a version in the [release
notes](https://run.camelai.com/docs/operations/release-notes.md); read the notes
of every version between yours and the new one, then set it in `.env` and
restart:

```bash
echo 'AGENT_RUNTIME_IMAGE=ghcr.io/qaml-ai/run:<version>' >> .env
docker compose up -d
```

The new container applies database migrations as it starts, and an older image
does not undo them, so back up first (below). On `docker compose stop`, the
runtime lets running turns finish (up to 100 seconds by default) before it
exits.

The SDKs (`@camelai/run`, `camelai-run`) are versioned apart from the runtime.
A new SDK can call API your runtime does not have yet: upgrade the runtime when
you upgrade the SDKs, or keep the SDK release from your runtime's date.

## Backup

Back up Postgres and the agents' data together (with `AGENT_STORAGE=s3`, the
bucket instead of the volume):

```bash
docker compose exec -T postgres pg_dump -U agent_runtime agent_runtime | gzip > runtime-$(date +%F).sql.gz
docker run --rm -v "${COMPOSE_PROJECT_NAME:-agent-runtime}_data:/data:ro" -v "$PWD:/out" alpine \
  tar czf "/out/data-$(date +%F).tar.gz" -C /data .
```

Restore into a stopped stack: `gunzip -c runtime-….sql.gz | docker compose exec -T
postgres psql -U agent_runtime agent_runtime` into an empty database, and the
tarball back into the `data` volume. Keep `AGENT_SECRETS_KEY` and
`AGENT_SESSION_SECRET` with the backup: stored keys cannot be read without the
first.

## Storage

By default, agents' data (transcripts, files and volumes) is on the `data`
volume, and indexes are in Postgres. Back up both.

For S3 or a service that speaks it, set `AGENT_STORAGE=s3`, `AGENT_S3_BUCKET`,
`AWS_REGION` and credentials, and for anything but AWS, `AGENT_S3_ENDPOINT` (and
`AGENT_S3_FORCE_PATH_STYLE=true` if the service wants path-style requests). The
service must support conditional writes (`If-None-Match: *`). AWS S3,
Cloudflare R2, SeaweedFS and RustFS do. `docker compose --profile s3 up -d` runs
SeaweedFS alongside, with the bucket made for you.

## Networking

**Private runtime.** Leave out the Compose file's `ports`, put your application
on the same Docker network, and call `http://runtime:8790`. Browsers then read
agents through your application, with
[`createAgentHandler({ proxy: true })`](/docs/camelrun/chat-app#read-through-your-route).
Set:

* `AGENT_PUBLIC_URL=http://runtime:8790`, the address your application and tool
  servers reach the runtime at.
* `AGENT_BROWSER_URL=` (empty), so browser tokens name no URL.

**Public runtime.** Route a hostname through a TLS-terminating proxy to port
8790 and set `AGENT_PUBLIC_URL` to that URL. Keep single sign-on off the
browser read routes (`/v1/agents/{id}/events`, `/state`, `/history`,
`/inputs`), since the browser token is their credential.

**Reaching your own services.** The runtime refuses to call private and
loopback addresses, so an agent can't reach your network. Allow the exact
origins of your own services it must call (a tool server, a model server) with
`AGENT_OUTBOUND_ALLOW_ORIGINS=http://app:3000,http://10.1.2.3:8000`. Each is
reachable at that scheme, host and port only. `web_fetch` and `web_search` never
use them, since the model chooses those URLs.

## More than one node

One node is the default and needs nothing more. Several nodes share Postgres
and S3 storage (`AGENT_STORAGE=s3`), each with `AGENT_NODE_URL`, the address its
peers reach it at. A load balancer in front sends each request to any node, and
nodes forward to the one serving an agent.
