Connection lost
Trying to reconnect…
Server didn't respond
Recovering…
Deploying emisar in production
The quickstart put one runner on one host. Production is a different question: dozens of hosts, real blast radius, an agent you won't hand a shell — and a security review that will ask exactly how a wrong call gets stopped. emisar's control plane is hosted, so you don't stand anything up; "deploying" here means two things — rolling the runner across your fleet, and setting the envelope around it. This page is the envelope.
None of it is much configuration. The defaults are already conservative — reads run,
mutations wait for a human, critical
is denied outright. Going to production is mostly deciding where
to tighten, and doing it once.
The shape of a production fleet
Two decisions carry most of the safety, and you make both up front: how runners are grouped, and who — which person, which agent key — is scoped to reach them. Get these right and the rest is defaults.
- — One runner per host. A runner's identity is its account plus a stable id; names are unique within an account. Don't share one across hosts, and don't run two on the same box.
-
—
Group by tier, not by host.
A group is the unit that policy, runbooks, member scopes, and agent fan-out all target.
Name it for the fleet role —
web,edge,db-prod,cache— not the machine. The installer defaults the group to the hostname; change it on your second host.web-01is a label, not a group. -
—
Labels for the cross-cutting facts.
Free-form
key=valuepairs —region=us-east-1,environment=prod,service=checkout— for filtering the fleet and targeting policy without redrawing your groups. - — Keep policy and access aligned. Policy decides what a runner will run; access decides who can reach it. A locked-down database tier should have both — a strict ruleset and only the DBAs (and their agent) in scope.
A three-tier layout, concretely — the same fleet three ways, each with the packs it needs and the posture it warrants:
nginx, linux-core, debugging.
Reads run on policy; a restart waits for one approval. The everyday tier.
postgres, linux-core. Locked: a targeted ruleset denies critical, four-eyes on high, and signed dispatch is on. Only the DBA team in scope.
shell
break-glass pack. Looser so an agent can iterate on a fix — never a path to a prod host.
Roll out in phases
Each phase is safe to stop at, and each is reversible. You never have a half-configured fleet that's more open than the phase before it.
- Pilot on one non-prod host. Install, watch it connect, run a read, open the audit row it wrote. Prove the loop end to end before it touches anything that matters.
-
Set the default policy.
Confirm the tier floor for the whole fleet —
low/mediumallow,highrequire approval,criticaldeny. Higher tiers can't be made more permissive than lower ones; the editor enforces it. - Add approvals where they matter. For anything that can drop a service or lose data, turn on four-eyes — two distinct approvers, self-approval off. Approvers get an email with the request; the on-call decides.
- Scope people and agents. Give each member and each MCP key only the runners or groups they own. Out-of-scope hosts don't render, 404 on their detail pages, and vanish from the dispatch surface — it hides the host, it doesn't just gray out a button.
-
Lock the sensitive groups.
Add a targeted ruleset for your production tiers. A ruleset replaces
the default for its target (most specific wins: runner over group over default), so the
db-prodgroup can denycriticaland four-eyeshighwhile the rest of the fleet stays on the default. - Turn on signed dispatch where it must be provable. On the hosts where "a real human asked for this" has to be cryptographically true — not just policy-checked — enforce signed dispatch. Those runners execute only a call carrying a valid Ed25519 signature the operator produced in their own client; the portal Run button, runbooks, and API keys stop dispatching there.
- Wire identity. Require MFA account-wide. Add SSO so people sign in through your IdP; on Enterprise, add SCIM so offboarding someone in the directory suspends them here — sessions killed, keys revoked — with no checklist. Only available on Team & Enterprise.
-
Ship the audit to your SIEM.
Mint an
audit:readkey, point your collector at the NDJSON feed, and alert on the handful of events that actually mean something (below).
Best practices, by layer
Runners & the network
-
—
The installer already runs the binary as a dedicated non-root
emisaruser under systemd. Keep it that way. Grant elevated rights per action through sudoers or polkit, not by running the runner as root. -
—
Runners dial out
over
:443. Nothing listens on the host — there's no inbound port and no bastion to stand up. Don't open one; there's nothing to open. -
—
Give packs their secrets through the runner's environment, never through call
arguments. Put them in
/etc/emisar/runner.env(mode0600) and allowlist the names inexecution.inherit_env. Only those names reach an action's process; the runner's own token and the rest of its environment never do. -
—
Set a local admission ceiling as a backstop.
admission.max_risk: mediumon a read-only box drops everyhigh/criticalaction before it can run — even one a compromised control plane tried to push. It's the host operator's veto over the cloud, baked in at boot.
Policy
-
—
Leave
criticaldenied. It's default-closed on purpose. Opt a single action out with a per-action override if you must — not the whole tier. -
—
Gate
high, don't allow it. A restart, a kill, a drain — one approval is cheap insurance against a bad call, human or model. -
—
Reach for per-action overrides sparingly and specifically
(
nomad.job_stop→require_approval), most specific first. The tier defaults should carry the bulk of the fleet, not a wall of exceptions. - — Use a targeted ruleset to lock a group, not to loosen one broadly. A ruleset replaces the default for its target; delete it to fall back. See policies & approvals.
Approvals
-
—
Four-eyes for anything destructive:
min_approvalsof 2, self-approval off. The person who asked can't wave through their own change. - — Standing grants are a scalpel, not a blanket. When you approve a repeat action, scope it tight — exact arguments, a use cap, the shortest window that fits the job. A 90-day any-arguments grant on a mutating action is a standing shell; don't mint one.
- — Let expiry do the cleanup. An undecided request cancels after 24 hours; a grant lapses on schedule. You don't have to remember to revoke — but you can, in one click, from the Approvals page.
Access & identity
- — Least-privilege roles: viewer to watch, operator to drive and decide approvals, admin to manage policy and the fleet, owner for billing. Most people are operators.
-
—
Scope every member and every key to the hosts they own. "The DBA agent only reaches
db-*" is one setting on the key — and out-of-scope hosts never appear. - — Require MFA account-wide — TOTP with one-time recovery codes; a replayed code is rejected. On Team and up, an SSO session can satisfy it when your IdP enforces MFA.
- — Wire your directory. SSO binds people to your IdP by issuer and subject, never by email. On Enterprise, SCIM lets the IdP drive the whole lifecycle: deactivate someone there and emisar suspends them here, kills their sessions, and revokes their keys — offboarding stops being a thing you can forget. See single sign-on & directory sync. Only available on Team & Enterprise.
-
—
Scope MCP keys per action and rotate them. A key carries
actions:read/actions:execute/audit:readand an optional runner restriction — so the log-shipping key never executes, and the agent key never sees a host it doesn't own.
Packs
- — Trust is deliberate, and that's the point. A pack runs only when its on-disk SHA-256 matches the hash you trusted on the Packs page; edit it and it goes pending until you re-trust. Leave that drift guard on — it's how a changed pack never runs silently.
-
—
Ship a host only the packs it needs. A web box has no use for the
postgrespack; a smaller catalog is a smaller surface and a shorter list for an agent to reason over. -
—
Keep the
shellbreak-glass pack off production. It's the one action where the operator supplies the whole command —critical-risk, default-denied — for verifying a fix on staging before you encode it as a real action. Install it on staging; never on a prod runner.
Audit
-
—
Stream it to your SIEM.
GET /api/auditis forward-only NDJSON with cursor paging; anaudit:readkey reads events and can't run anything, so it's safe to hand to a Splunk or Datadog collector. -
—
Keep the host journal too.
/var/log/emisar/events.jsonlis hash-chained — each line sealed by the one before it — so a cut or an edit is detectable withemisar audit verify. The cloud audit is the searchable system of record; the journal is on-host forensics that doesn't lean on the cloud to exist. -
—
Alert on signal, not noise:
policy.updated(someone changed the rules), pack-trust transitions (new bytes approved), and the most interesting one — a runner logging repeatedvalidation_failedoraction_blocked_by_admission: an agent asking, over and over, for things it can't have.
Two worked examples
An on-call agent that reads the web tier and restarts behind approval
Goal: your incident agent should investigate nginx freely, and be able to restart it — but a restart should need a human nod.
-
—
Fleet:
the ten web hosts share group
web, packsnginx+linux-core+debugging. -
—
Policy:
the defaults already fit — log tails and status reads are
low/medium(allow);nginx.restartishigh(require approval). No override to write. -
—
Access:
one MCP key,
actions:read+actions:execute, restricted to thewebgroup. That's the whole grant.
Result:
the agent calls nginx.error_log_tail
and it runs; it calls nginx.restart
and the run holds for the on-call, who sees the host, the exact arguments, and the agent's
reason before approving. Every call lands in the audit with its client, key, and reason.
A destructive action on prod-db, done safely
Goal:
a Postgres failover on db-prod
should be possible — but only when two people agree and a real human signed the request.
-
—
Fleet:
group
db-prod, three hosts, packspostgres+linux-core. Signed dispatch enforced on all three. -
—
Policy + approvals:
a targeted ruleset for the group —
criticaldenied,highrequire approval — with four-eyes on: two approvers, self-approval off. -
—
Signing:
each DBA holds a 24-hour leaf certificate from your offline CA in their MCP client;
the CA's public key sits in every
db-prodrunner's config, shipped through normal config management.
The flow:
a DBA's agent calls postgres.promote_replica
with a reason; the call carries the DBA's Ed25519 signature. The control plane relays it —
it can't forge or alter it, and holds no key to try. Policy holds the run for approval;
two other operators approve. The signed, twice-approved run dispatches; the runner verifies
the signature against its CA, checks the nonce hasn't been seen and the timestamp is fresh,
then runs it. The audit shows the requester, both approvers, the signature, and the
redacted command. If the cloud were compromised it could refuse to relay the call — but it
could never have originated that failover.
Go-live checklist
The list to walk before you point production at emisar — the same one a security reviewer will:
-
Every runner runs as the non-root
emisaruser — the installer default; confirm nobody changed it. - Runners are grouped by tier; every production group carries its own locked ruleset.
-
criticalis denied fleet-wide;highrequires approval. - Four-eyes is on for any group that can lose data or drop a service.
- Members and MCP keys are scoped to the hosts they own — nothing is account-wide by accident.
- MFA is required; SSO / SCIM is wired if you're on Team or Enterprise.
-
Pack secrets live in
runner.env+inherit_env, never in call arguments. -
The
shellbreak-glass pack is on no production runner. - Signed dispatch is enforced wherever a human's signature must be provable.
-
Audit is streaming to your SIEM through an
audit:readkey, with alerts on policy changes and admission blocks.
What emisar is not
Worth saying plainly, because a good reviewer will ask. emisar bounds what can be run and keeps the record; it doesn't replace the layers underneath it.
- — Not a sandbox. A restart still restarts the service. emisar bounds what can be called and records it; process isolation is the host's job — systemd hardening, namespaces, SELinux.
- — Not an EDR. It won't spot a malicious binary or lateral movement. It will refuse a pack whose bytes drifted from what you trusted, and reject an argument its schema doesn't allow.
- — Signed dispatch is integrity, not availability. A compromised control plane can refuse to relay a signed call; it can't forge one. And there's no revocation list — short certificate TTLs (24h) are how a leaked key stops mattering.
- — Redaction is pattern-bound. Built-in rules mask tokens and keys before output leaves the host, but a secret in a shape no rule matches will slip through — so design actions not to print secrets, and pass them through the environment.
- — The cloud audit is searchable and append-only, not tamper-evident. The hash-chained journal on the host is the tamper-evident record — and even that a root attacker can delete. Ship it off-box if you need a copy beyond their reach.