Reconnecting
Restoring connection…
Reconnecting
Restoring connection…
Docs navigation
Get started
Connect an LLM
Operate
Build packs
Run in a container
The runner is a plain foreground process — it runs fine in a container. What changes isn't whether it runs; it's what it can see.
$ docker run -d --name emisar \ -v emisar-data:/var/lib/emisar \ -e EMISAR_URL=https://emisar.dev \ -e EMISAR_ENROLLMENT_KEY=emkey-enroll-… \ you/emisar-runner $ docker logs -f emisar
emisar.dev
like any other runner.
What a containerized runner sees#
A runner can only act on what its own namespace can reach. On a VM or bare metal that's the whole machine — which is why the host install is the default: most packs are host-oriented, and some literally require a systemd host. Put the same runner in a bare container and those packs now see the container — its processes, its filesystem, its network.
It gives you the freedom to design the runner scope that fits your specific use case. A sidecar that can only see one app is a tightly scoped runner — the agent operating through it can't touch anything else. You just have to pick the shape that matches what you want the agent to operate: one app, a node, or every node.
The two mechanics every shape shares#
First, skip the service.
The default installer creates a systemd service, so on a host without a real init it
refuses with "this looks like a container or cloud shell"
— that check is about
the installer, not the binary. Pass
--no-service
and it skips init detection, the service user, and the unit, and just puts the binary in
place. Supervision then belongs to whatever runs the container: docker run --restart, a Kubernetes restart policy, or the
Nomad client.
$ curl -sSL https://emisar.dev/install.sh | sudo bash -s -- --no-service
Second, preserve connection state.
By default, the runner uses the container hostname as its identity. After the first connect,
the per-runner token lands at cloud.token_path
(/var/lib/emisar/token.json
by default). Keep the hostname stable and /var/lib/emisar
in a volume so restarts preserve the same runner and its durable dispatch state. Pin
runner.id
when the orchestrator changes hostnames across placements but the workload is still one
logical runner:
schema_version: 1 runner: id: "payments-sidecar" group: "sidecars" paths: data_dir: "/var/lib/emisar" events: jsonl_path: "/var/log/emisar/events.jsonl" cloud: enrollment_key_env: "EMISAR_ENROLLMENT_KEY" token_path: "/var/lib/emisar/token.json"
One more thing before a fleet: the dashboard's Connect-a-runner key is single-use — right for one host, wrong for a template that scales. For DaemonSets and system jobs, mint a reusable enrollment key (optionally capped at a number of uses) and hand that to the template — see enrollment keys.
A sidecar next to one app#
When the thing you want the agent to operate is the app — read its logs, inspect its processes, poke its admin CLI — run the runner as a second container that joins the app container's PID, network, and (read-only) volumes. The runner then sees exactly that app and nothing else; policy, approvals, and audit work unchanged.
$ docker run -d --restart unless-stopped \
--name emisar-runner \
--pid container:payments \
--network container:payments \
--volumes-from payments:ro \
-e EMISAR_URL=https://emisar.dev \
-e EMISAR_ENROLLMENT_KEY=emkey-enroll-… \
-v /etc/emisar/config.yaml:/etc/emisar/config.yaml:ro \
-v emisar-runner-data:/var/lib/emisar \
your-registry/emisar-runner
emisar does not publish a universal runner image. Build yours from the release binary with exactly the tools its selected packs call; the command above supplies its operator-owned config and persistent token volume. The image contents are part of the runner's capability boundary, so keep them as narrow as the workload allows.
One per node: Kubernetes and Nomad#
To operate the nodes instead of a single app, run one runner per node — a Kubernetes DaemonSet or a Nomad system job. Both reuse the two mechanics above (a service-less binary and a persistent state volume) and a reusable enrollment key; each has its own worked manifest:
-
—
Kubernetes
— a DaemonSet whose pod spec is your blast-radius dial:
hostPID/hostNetworkand read-only host mounts decide exactly what each node's runner can see. -
—
Nomad
— a
systemjob placing one instance on every client node, with the release binary fetched in anartifactstanza and identity persisted on a host volume.