Self-hosting
Self-host camelRun
Run the camelRun runtime on your own host with Docker Compose and Postgres
View as Markdown · Docs index for coding agents
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). 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.
What you need
- Docker Engine with Compose v2, on
linux/amd64orlinux/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).
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/
in the repository has a Compose file and an example environment:
Configure
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.bakSet 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.
Start the runtime
docker compose up -d
curl -s localhost:8790/healthzThe 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:
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:
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 modelEverything in the 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; read the notes
of every version between yours and the new one, then set it in .env and
restart:
echo 'AGENT_RUNTIME_IMAGE=ghcr.io/qaml-ai/run:<version>' >> .env
docker compose up -dThe 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):
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 }).
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.