DEMO/Infrastructure

← Back to dashboard

Config Management runners

Audience: operator · Profile: both · Read this when: you are choosing or configuring the runner a job executes on.

Part of Config Management.

Where the Ansible process actually runs. Picked in Settings → Ansible → Runner.

Runner Where it runs Best for
Local Docker Inside the dashboard container's Docker context. Uses a side-car chrweav/ansible-winrm container per run (ansible + pywinrm). On-prem hypervisor targets; corporate-network targets; anything reachable from the dashboard host.
AWS ECS Fargate A Fargate task launched per run in your VPC. EC2 targets in private subnets without a path back to the dashboard host.
Azure ACI An Azure Container Instance per run, in your VNet. Azure VMs in private subnets.
GCP Cloud Run Jobs A Cloud Run Job per run, in your project. GCE instances.
Remote agent A one-shot container on the agent's host, inside your network. Not selectable here — it is chosen automatically when the target is only reachable that way. Which agent defaults to the one that discovered the VM, and can be routed per address range on Remote Agents → Config Routes. On-prem hypervisor guests and on-prem databases, especially from a cloud-hosted dashboard; and cloud VMs and databases a cloud route sends to an agent in their network. See remote agents and which agent executes a run.

The cloud runners exist because connecting from a dashboard sitting on a corporate LAN to a deeply-private cloud subnet is often impossible without a VPN. Running the playbook inside the cloud avoids that network problem at the cost of one Fargate task / ACI / Cloud Run invocation per run.

The runner choice constrains the storage choice. The runner has to fetch the asset before executing it, which means the runner needs network reachability to the storage backend. Two combinations don't work:

Storage Runner Outcome
Local Filesystem / UNC ECS / ACI / Cloud Run Refused (the cloud runner has no path back to the corporate file server). The dashboard surfaces this as a disabled radio + 400 on the API.
Cloud bucket (S3 / Blob / GCS) Local Docker Works fine. Fetches go out over the dashboard host's normal egress.

Most teams pair Local Docker + Local Filesystem (or a cloud bucket) for on-prem labs, and one of the cloud runners + a cloud bucket for cloud fleets.

A Password Safe managed account on the ECS or Cloud Run runner needs one of two delivery paths, and the run is refused until one is on:

Both are under Settings → Ansible; how each works, and what each one trusts, is in Secrets management. ACI injects the credential inline and needs neither.

Why one-shot runners (the security argument)

Every runner — Local Docker, ECS Fargate, ACI, Cloud Run Jobs — is ephemeral by design. A new container is spawned per run; it executes the playbook; it exits and is destroyed. Nothing persists between runs.

This is deliberate, and it matters. Long-lived runners are a known weakness in CI/CD and config-management estates: they accumulate secrets in environment variables, cached SSH keys in ~/.ssh, remembered hosts in known_hosts, leftover state from previous runs. A single compromise of a long-lived runner can yield credentials that have been touched by every job that ever ran on it. The community edition's design refuses to be that target:

The compliance angle: regulations like SOC 2 CC6.1, NIST SP 800-53 AC-6 (least privilege) and SC-39 (process isolation), and CIS Controls 4.1 / 4.7 all point at "minimise persistent privileged surface". An ephemeral runner satisfies them by construction — there's nothing persistent to harden, audit, or rotate. Auditors generally accept "the runner has a 90-second lifespan and zero state at rest" with less friction than "here's our hardening baseline for the long-running worker fleet."

Compared to common alternatives:

Approach Persistent attack surface Secret-at-rest in runner Per-run isolation
Dedicated CI worker (e.g. self-hosted GitHub runner) The whole VM Cached creds, SSH known_hosts, build artefacts Best-effort cleanup scripts
Always-on Ansible Tower / AWX Platform process Vault-decrypted secrets in process memory Within the platform's job isolation
Dashboard's one-shot runner None — container is gone Tmpfs, lifetime of run Container per run; no shared FS

This isn't unique to the dashboard at the technical level — the underlying primitives (Fargate / ACI / Cloud Run Jobs / docker run --rm) have been around for years. What's notable is the design decision to only offer ephemeral runners. There's no escape hatch in the codebase for "give me a long-lived worker for performance reasons." You pay a one-second startup penalty per run; you never have to defend a fleet of long-lived runners to a security review.

The one long-lived process, and why it doesn't contradict this

Remote agents run a persistent container inside a customer's private network. That looks like the exact thing this section argues against, so it is worth being precise about why it isn't.

Read the sentence above again: no escape hatch for a long-lived worker for performance reasons. An agent is not asking for persistence to save a second of startup. It asks for it for reachability — you cannot launch a one-shot container inside a network you cannot reach, so something has to already be there. That is a different justification, and the four invariants this section actually names all survive it:

Invariant above How the agent holds it
Secrets fetched just-in-time The agent stores no target credential. For a run that needs one (a cloud VM's SSH key, a dashboard-held secret), the dashboard seals it to that agent for that one job, the agent copies it into the one-shot run container, and it goes with the container. With Release dashboard-held credentials only to SPIRE-attested agents on, only an attested agent receives one
No process or filesystem outlives the run Nothing is written per job; the only persistent state is the agent's own signing key
No shared user namespace between runs Agent-executed Ansible spawns a one-shot sibling container per job; the supervisor never runs a playbook itself
Dashboard never holds the secret long Strengthened, in fact: refs are resolved on demand and the agent fetches them for the life of one child container

So the runner is still one-shot. What moved is the thing that launches it — from the dashboard's Docker socket to one inside the network the target actually lives on. The supervisor that does the launching is small, holds no credentials, and executes nothing the dashboard sends it: the job payload is a closed allowlist of scalars and network addresses, and its handler table is a closed dict rather than a dispatch on a string from the wire.