Reconnecting
Restoring connection…
Reconnecting
Restoring connection…
Docs navigation
Get started
Connect an LLM
Operate
Build packs
Install on a host
The quickstart gets one runner online in a minute. This page is the full install. For a container instead of a host, see Containers.
Use your coding agent
The install-emisar
skill walks your agent through this whole page: install, verify, evidence report.
Install the emisar runner and verify it connects. First ask me which host to install it on and how you can reach it (local shell, SSH, or something else). My enrollment key: emkey-enroll-… (I can mint one at Connect a runner in the emisar dashboard). Use the install-emisar skill: https://raw.githubusercontent.com/AndrewDryga/emisar/main/skills/install-emisar/SKILL.md
Works in Claude Code, Codex, or any agent that reads Markdown skills — read the skill first .
- A Linux host with systemd — a VM, a cloud instance, or bare metal. (macOS + launchd works for dev/eval; containers work too — see Containers & Kubernetes.)
-
sudoon that host. -
Outbound HTTPS to
emisar.dev:443for the runner (it dials out; nothing listens on the host, so there's no inbound port to open), plusapi.github.comandgithub.comfor the installer — it downloads the runner release and checksums from GitHub.
The install command#
One command, run as root, on a Linux host with systemd. The Runners → Connect a runner button generates this exact line with a fresh single-use key baked in.
$ curl -sSL https://emisar.dev/install.sh \
| sudo EMISAR_ENROLLMENT_KEY=emkey-enroll-… EMISAR_URL=https://emisar.dev bash
Verify this download first
The installer checks the checksum itself; to prove the binary before it runs as sudo, download it and run these
first — a green check names our source repository and the release workflow that built it.
# provenance — built by our workflow, from our source $ gh attestation verify emisar-<version>-linux-amd64.tar.gz --owner andrewdryga # checksums — the bytes match what we published $ sha256sum -c SHA256SUMS
More on the signing pipeline: Release integrity.
What does the install script do?
It's a plain, readable shell script — read it first if you'd rather. Here's what it sets up:
-
—
Linux + systemd (the production target).
Creates an unprivileged
emisarsystem user, drops the binary in/usr/local/bin/emisar, and installs a hardenedemisar.serviceunit that runs as that user withRestart=on-failure. systemd is required — the installer checks for it and stops if it's missing, pointing you at--no-servicefor hosts you supervise yourself. -
—
macOS + launchd (dev / eval).
Installs the binary in
/usr/local/binand aLaunchDaemonat/Library/LaunchDaemons/com.emisar.runner.plistwithKeepAlive. Good for trying packs on a laptop; production runs on Linux. -
—
Config + secrets.
Writes
/etc/emisar/config.yaml(the non-secret settings) and/etc/emisar/runner.env(mode 0600 — the enrollment key and any pack credentials). State and logs live under/var/lib/emisarand/var/log/emisar.
Flags and environment#
Pass flags after bash -s --;
pass values as environment variables before bash. The ones you'll reach for:
| Flag / variable | What it does |
|---|---|
| EMISAR_URL | Your control-plane URL — baked into config.yaml (default https://emisar.dev). |
| EMISAR_ENROLLMENT_KEY | The emkey-enroll-… key the runner trades for its own token on first connect. |
| RUNNER_GROUP | The cloud UI's auto-grouping key, written to runner.group. Defaults to the host's short hostname. |
| RUNNER_LABEL_<KEY> | Bake a free-form runner label into config.yaml, e.g. RUNNER_LABEL_ROLE=web or RUNNER_LABEL_ENVIRONMENT=prod. Set as many as you like; the cloud UI filters and searches on them. |
| --packs LIST | Install these packs during setup, e.g. --packs linux-core,docker. |
| -y / --yes | Skip the confirmation prompts for unattended installs. Requires EMISAR_PACKS set explicitly (even to empty). |
| --version TAG | Pin a specific release instead of latest. |
| --no-start | Install everything but don't start the service yet — for image bakes. |
| --no-service | Binary only: skip the service user, unit, and activation. For containers and CI — see Containers. |
Rarer knobs — path overrides (--bin-dir, --etc-dir, --data-dir, --log-dir), a custom service user (--user), and
EMISAR_GITHUB_TOKEN
for GitHub API rate limits — are listed by install.sh --help.
The config file#
/etc/emisar/config.yaml
is operator-owned and readable — nothing secret lives here (the key rides in runner.env). The fields you'll
actually set:
schema_version: 1 runner: group: "web" # the fleet tier this host belongs to (targeted by runbooks + scopes) labels: # free-form, for finer targeting role: "frontend" region: "us-east-1" # id: "web-01" # optional — defaults to the current hostname cloud: url: "https://emisar.dev" enrollment_key_env: "EMISAR_ENROLLMENT_KEY" execution: inherit_env: # env-var names a pack may read for credentials - NOMAD_ADDR - NOMAD_TOKEN # admission: # optional local defense-in-depth — see the security model # signing: # optional signed-dispatch enforcement — see signed dispatch
Restart the service after editing (sudo systemctl restart emisar,
or SIGHUP to reload packs without dropping runs). Two fields have their own pages:
admission
(a local allow/deny list and risk ceiling the cloud can't override) and
signing
(require a customer signature on every dispatch).
The runner's user and privileged actions#
The service runs as the unprivileged
emisar
user, not root — so by default an action can read logs, list processes, and query services,
but can't touch anything that needs privilege. When a pack action legitimately needs root —
restarting a system service, editing system config — you grant that one capability
explicitly, so the blast radius is a list you wrote, not "the runner is root":
# let the runner restart exactly nginx, nothing else
emisar ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
The action calls sudo systemctl restart nginx;
a polkit rule works the same way. On Linux, an action with a
user:
set also drops to that user before exec, so even a runner under a broad account runs the
declared action as the lower-privilege user. The full host defense-in-depth picture —
setuid drop, local admission, systemd hardening — is in the security model.
After it's connected#
Everything past the first connect — groups and labels, pack credentials, updating the binary and packs, reconnect behavior, the host-side CLI, and clean removal — lives on the Runner fleet page. To boot ephemeral hosts from an autoscaling group with one reusable key, see autoscaling fleets; to roll out across a fleet in phases, see the production rollout.