Skip to main content
Docs navigation

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.

Before you start, you need:
  • A Linux host with systemd, such as a VM, cloud instance, or bare-metal host. macOS with launchd works for development and evaluation.
  • sudo on that host.
  • Outbound HTTPS to emisar.dev:443 for the control plane, installer, and release files, plus registry.emisar.dev:443 for 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.

paste into your agent
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:

shell
$ 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.

Linux
      # 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 emisar system user, puts the binary at /usr/local/bin/emisar, and installs a hardened emisar.service unit (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.yaml and the enrollment key to /etc/emisar/runner.env (mode 0600). State and logs live under /var/lib/emisar and /var/log/emisar;
  • installs the host-matched starter packs (on a typical Linux host: linux-core, debugging, and systemd-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:

emisar console · Runners
A connected host shows a green connected badge with its last heartbeat; offline hosts stay listed with when they were last seen.
shell
$ 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:

/etc/emisar/config.yaml
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:

/etc/polkit-1/rules.d/50-emisar.rules
// 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.

Last reviewed September 3, 2026