Add a host
A host is a machine where agents run. It is one container that connects out to the server over a WebSocket, so it works behind NAT with no open port, and it holds nothing of yours: a cache volume (repository mirrors, engine installs, its own token) and, per run, a folder in memory that is gone at the end. Losing a host loses nothing but the runs it had in flight.
The first host
Section titled “The first host”The installer and the Coolify file start one host next to the server. It joins by itself once your account exists, with the one-use code written in the installation’s .env. It reaches the server over the compose network, not through the internet.
Another host
Section titled “Another host”In the app, System → Hosts lists your hosts with their state (online or not, last seen, runs at work, sandbox, version).
-
Get the line. Press Add host. The page makes a join code and shows the
docker runline that carries it, with Copy and a countdown: the code works once, for ten minutes. If the server does not know its host image, the page says to build it first from the clone (docker build -f apps/host/image/Dockerfile -t kedalio-host:local .). -
On the new machine, run the line in a shell (
bashorsh). It needs only Docker, no clone: it reads the seccomp profile out of the host image itself and removes the temporary file after. On a machine with AppArmor, the host’s profile is used when it is installed (/etc/apparmor.d/kedalio-host, see below); without it Docker’s own profile applies, which forbids the mounts the sandbox needs, so the host starts, says its sandbox failed and takes no run. -
Or use the compose file, from a clone,
apps/host/image/, to name the host and have Compose build the image:Terminal window KEDALIO_SERVER=https://app.example.com KEDALIO_JOIN=<code> KEDALIO_NAME=box2 docker compose up -dKEDALIO_HOST_IMAGEnames the image to run; without it, Compose builds it from the clone. The name is letters, digits,.,_and-, at most 63; without one, the container’s hostname is used.
The host trades the code for its own token, keeps it in its cache volume, and from then on starts without KEDALIO_JOIN. A host started with no token and no code stops and says: no host token in … and no KEDALIO_JOIN: add the host from the app.
The sandbox
Section titled “The sandbox”Every agent runs in a sandbox inside the container: Claude Code with its own sandbox, which it may not turn off, and OpenCode and Pi in a bubblewrap jail. That is why the container needs its security options: no capabilities, no-new-privileges, a seccomp profile that is Docker’s default plus the calls bubblewrap needs, systempaths=unconfined, and, on a machine with AppArmor, the host’s profile. To install it, from a clone’s apps/host/image/, once:
sudo install -m 0644 apparmor/kedalio-host /etc/apparmor.d/kedalio-hostsudo apparmor_parser -r -W /etc/apparmor.d/kedalio-hostThe host checks its sandbox at start and every hour; when the check fails it takes no run. You can run the same check by hand:
docker run --rm --cap-drop ALL --security-opt no-new-privileges \ --security-opt seccomp=seccomp.json --security-opt systempaths=unconfined <host image> probesandbox: ok is what a host needs.
A run never reads the host’s token, and it pushes only its own branch, kedalio/<run-id>: never main, master, dev, beta or release, never a deletion.
How many runs a host takes
Section titled “How many runs a host takes”Capacity comes from the container’s memory: (memory − 2 GiB) ÷ 1.5 GiB sessions, at least one. The host’s compose file gives 8 GB (4 sessions) and 4 CPUs by default; change them with KEDALIO_HOST_MEMORY and KEDALIO_HOST_CPUS, the same names as the server’s files (KEDALIO_MEMORY and KEDALIO_CPUS, the older names, still work).
A host can also refuse work whatever the server says:
| Variable | What the host refuses |
|---|---|
KEDALIO_ALLOW_REPOS |
repositories not in the list, host/owner or host/owner/repo, comma-separated |
KEDALIO_ALLOW_ENGINES |
engines not in the list: claude-code, codex, opencode, pi |
KEDALIO_MAX_RUNS |
more runs at once than this |
A new run goes to an online host whose sandbox passed, that has the engine, is within its limits and has a free session; among those, the one with the most room. A squad that names its host waits for it.
Updates
Section titled “Updates”Hosts follow the server’s version by themselves. When the server says which version it wants, the host downloads that bundle from the server, checks its SHA-256, waits until no run is in flight, and restarts on it. The engines are installed into the cache at the versions the server pins (in its code; KEDALIO_ENGINES overrides). A new host image, with newer system packages, still comes from pulling the image again.
Offline, joining again, removing
Section titled “Offline, joining again, removing”- A host sends a heartbeat every 20 seconds and reconnects by itself. The server drops a host silent for a minute; its runs are interrupted after ten minutes offline.
- Joining again with a new code and the same name keeps the host and replaces its token. A host whose token the server no longer accepts (revoked) deletes it and joins again by itself when it has
KEDALIO_JOIN; without one it says in its log that it was revoked and needs a new join code, and stops retrying. A token the server still accepts wins overKEDALIO_JOIN. - Removing a host is Revoke on its row in System → Hosts. The host goes offline now, its runs stop being reported and its token stops working at once; stopping the container is yours.
Not built yet: «use this computer as a host» in a desktop app.