Skip to main content
Docs navigation

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.

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

Before you start, you need:
  • 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.)
  • sudo on that host.
  • Outbound HTTPS to emisar.dev:443 for the runner (it dials out; nothing listens on the host, so there's no inbound port to open), plus api.github.com and github.com for 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.

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

shell
# 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 emisar system user, drops the binary in /usr/local/bin/emisar, and installs a hardened emisar.service unit that runs as that user with Restart=on-failure. systemd is required — the installer checks for it and stops if it's missing, pointing you at --no-service for hosts you supervise yourself.
  • macOS + launchd (dev / eval). Installs the binary in /usr/local/bin and a LaunchDaemon at /Library/LaunchDaemons/com.emisar.runner.plist with KeepAlive. 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/emisar and /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:

/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 — 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":

/etc/sudoers.d/emisar
# 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.