Docs navigation
Get started
AI agents
Connect
Operate
Day to day
When it breaks
Govern access
Team & account
Access
Identity concepts
Provider guides
Account
Linux host
The quickstart gets one runner online in a minute. This page has the rest: the install flags, the config file, and the privileges you grant.
For a container instead of a host, see Install in a container.
- A Linux host with systemd, such as a VM, cloud instance, or bare-metal host. macOS with launchd works for development and evaluation.
-
sudoon that host. -
Outbound HTTPS to
emisar.dev:443for the control plane, installer, and release files, plusregistry.emisar.dev:443for action packs. See Network requirements for optional attestation verification.
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 console). 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 .
The install command#
In the console, click Runners → Connect a runner. It shows this command with a fresh single-use key filled in. Run it as root on the host:
$ curl -fsSL https://emisar.dev/install.sh \
| sudo EMISAR_ENROLLMENT_KEY=emkey-enroll-… bash
Verify this download first
The installer runs these checks before the binary can run as sudo. Bundle verification uses GitHub CLI with gh attestation verify --bundle; without it the installer asks before continuing on the checksum alone, or warns and continues when run unattended. GitHub CLI needs no GitHub login, but a fresh cache loads public trust roots from the hosts listed under Network requirements.
# signed checksum metadata — produced for this tag by the trusted workflow
$ gh attestation verify SHA256SUMS --bundle SHA256SUMS.sigstore.jsonl \
--repo andrewdryga/emisar \
--signer-workflow AndrewDryga/emisar/.github/workflows/runner-release-trusted.yml \
--source-ref refs/tags/runner-v<version> \
--deny-self-hosted-runners
✓ Verification succeeded!
…
# archive bytes — match the authenticated checksum
$ awk -v file='emisar-<version>-linux-amd64.tar.gz' \
'$2 == file { print; found=1 } END { exit !found }' SHA256SUMS \
| sha256sum -c -
emisar-<version>-linux-amd64.tar.gz: OK
…
More on the signing pipeline: Release integrity.
What does the install script do?
It's a plain shell script, and you can read it first if you'd rather. It:
- — checks the archive against its SHA-256 checksum and, when GitHub CLI is installed, authenticates the signed checksum first;
-
—
creates an unprivileged
emisarsystem user, puts the binary at/usr/local/bin/emisar, and installs a hardenedemisar.serviceunit (Restart=on-failure). Without systemd it stops and points you at--no-service. On macOS it installs a LaunchDaemon instead — fine for a laptop; production runs on Linux; -
—
writes the settings to
/etc/emisar/config.yamland the enrollment key to/etc/emisar/runner.env(mode 0600). State and logs live under/var/lib/emisarand/var/log/emisar; -
—
installs the host-matched starter packs (on a typical Linux host:
linux-core,debugging, andsystemd-deep); - — starts the service, which connects out to the control plane.
Confirm it connected#
The runner connects out as soon as its service starts. In the console, open Runners — the host appears online, with the actions it advertises. On the host, run:
$ sudo emisar doctor
It checks the config, credential, packs, binaries, and service in one pass. The install finished but the host never appears — Troubleshooting starts from that symptom.
The config file#
/etc/emisar/config.yaml
is the configuration file, and you can edit it. It does not contain secrets — they live
in runner.env. The fields
you will 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 — the runner's declared name + identity; defaults to the hostname cloud: url: "https://emisar.dev" enrollment_key_env: "EMISAR_ENROLLMENT_KEY" execution: inherit_env: # env-var names a pack can 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).
admission
sets a local allowlist, denylist, and risk ceiling that the control plane cannot override
— see the security model.
signing
makes the runner refuse any dispatch not signed with your key — see signed dispatch.
Flags and environment#
Flags go after bash -s --; environment variables go before bash. The common options are below. For the complete installer argument list, see
Install, update, and removal
in the Runner CLI reference.
| Flag / variable | What it does |
|---|---|
| EMISAR_URL | The control plane to enroll against, baked into config.yaml. Defaults to https://emisar.dev; set it only for a test or evaluation portal. |
| EMISAR_ENROLLMENT_KEY | The emkey-enroll-… key the runner exchanges for its own token on first connect. |
| EMISAR_GROUP | How the console groups runners, written to runner.group. Defaults to the host's short hostname. |
| EMISAR_RUNNER_ID | Registers the runner under a declared name and identity, written to runner.id. Defaults to the hostname — declare one for containers and hosts with colliding hostnames. |
| EMISAR_RUNNER_LABEL_<KEY> | Bake a free-form runner label into config.yaml. For example, use EMISAR_RUNNER_LABEL_ROLE=web or EMISAR_RUNNER_LABEL_ENVIRONMENT=prod. The console filters and searches these labels. |
| --packs LIST | Install named packs during setup. For example, use --packs linux-core,docker. |
| -y / --yes | Skip the confirmation prompts for unattended installs. Requires an explicit --packs LIST or EMISAR_PACKS (an empty value installs none). |
| --version TAG | Pin a specific release instead of latest. |
| --no-start | Install everything but do not start the service yet — for image bakes. |
| --no-service | Binary only: skip the service user, unit, and activation. For containers and CI — see Containers. |
Run
install.sh --help
for path overrides and a custom service user. Set
EMISAR_GITHUB_TOKEN
only if you use the optional GitHub release fallback and hit its rate limits.
The runner's user and privileged actions#
The service runs as its own
emisar
user, not root, and every action runs as that user. By default an action can do what any
user on the host can: read logs, list processes, check service status. The runner also
blocks every action from gaining privileges through
sudo
or any other setuid program — a sudoers grant for the
emisar
user does nothing. When a pack action needs root, there are two ways in.
For systemd operations, grant exactly the operation with a polkit rule — systemd asks polkit who may manage its units, and no privilege changes hands in the action:
// let the emisar user restart exactly nginx, nothing else
polkit.addRule(function(action, subject) {
if (subject.user == "emisar" &&
action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "nginx.service" &&
action.lookup("verb") == "restart") {
return polkit.Result.YES;
}
});
The action then calls
systemctl restart nginx
and polkit allows exactly that call. For packs that need more than systemd verbs, run the
service as root instead and let policy, approvals, and the local admission list bound what
runs. An action that declares a
user:
drops to that user before it runs, even when the runner account is broader. The rest of
the host protections — the setuid block, the user drop, local admission, systemd hardening
— are in the security model.
After it is connected#
Manage the runner fleet covers work after the first connection. It explains groups, labels, pack credentials, updates, reconnects, the host CLI, and removal. 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 Go to production.