What you get
A Connected Runner executes your pipelines on a Docker engine you own — a server, a VM in your cloud account, a workstation. Your own storage keeps job inputs and outputs in a bucket you control. Compute that is yours is not metered in credits: you pay the flat platform fee only. Marketplace pipelines are the exception: they always run on Marathoon's hosted compute.
Prerequisites
- A machine with Docker 24+ (Linux, or Windows with Docker Desktop in Linux-containers mode)
- Outbound HTTPS from that machine to the Marathoon API and to the registries your job images come from
- Nothing inbound: no open port, no public IP, no VPN
- A plan that includes Bring your own infrastructure (Pro and up) and the Manage providers permission
1. Start the guided setup
Go to Settings → Infrastructure → Connect infrastructure → Runner, give the machine a name, and click Generate enrollment token. The token is valid for 15 minutes and can be used once; it is shown a single time.
2. Install the runner
Pick the method that fits the machine. All three do the same thing: install the agent, hand it the token, and let it enrol itself.
Docker (any Linux host or VM)
docker run -d --name marathoon-runner --restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/marathoon/runner-tmp:/var/lib/marathoon/runner-tmp \
-v marathoon-runner-config:/etc/marathoon-agent \
-e MARATHOON_API_URL='https://api.example.com' \
-e MARATHOON_AGENT_NAME='build-box-01' \
-e MARATHOON_ENROLL_TOKEN='mre_…' \
ghcr.io/lordinaire/marathoon-runner:latest
The staging directory (/var/lib/marathoon/runner-tmp) must be mounted at the same path on the host: the runner hands it to the host's Docker daemon as a bind mount for each job.
Linux package (Debian/Ubuntu or RHEL family)
sudo apt install ./marathoon-agent_amd64.deb # or: sudo dnf install ./marathoon-agent.x86_64.rpm
sudo MarathoonAgent enroll --api-url 'https://api.example.com' --token 'mre_…'
sudo systemctl restart marathoon-agent
The package installs a hardened systemd service running as its own user, adds it to the docker group, and stages job files under /var/lib/marathoon-agent/work.
Windows (Docker Desktop)
Run the installer with the values from the wizard, or paste them into its Connect to Marathoon page:
.\MarathoonAgentSetup.exe /SILENT /APIURL="https://api.example.com" /ENROLLTOKEN="mre_…"
Share C:\ProgramData in Docker Desktop's file-sharing settings so job containers can see the staging folder.
Kubernetes (Helm)
No Docker socket at all: the chart deploys one runner pod, and every job runs as its own Kubernetes Job in the namespace.
helm upgrade --install marathoon-runner oci://ghcr.io/lordinaire/charts/marathoon-runner \
--namespace marathoon --create-namespace \
--set-string marathoon.apiUrl='https://api.example.com' \
--set-string runner.name='cluster-runner' \
--set-string marathoon.enrollToken='mre_…'
Job pods get an emptyDir workspace; the runner streams inputs in and outputs out through the exec API (the kubectl cp mechanism), so job pods hold no Marathoon credential and mount no service-account token. The runner's Role is limited to jobs, pods, pods/exec, pods/log and secrets in that namespace — give it a namespace of its own. Enrolled credentials are kept in the Secret marathoon-runner-credentials, so pod restarts keep their identity; set jobs.resources if the namespace has a ResourceQuota.
3. Watch it appear
The wizard polls until the machine has enrolled and sent its first heartbeat with its execution backend (Docker, or the Kubernetes API) reachable. If Docker shows as unreachable: check the socket mount (Docker), add the service user to the docker group (package), or make sure Docker Desktop is running (Windows).
4. Route jobs to it
Choose whether the runner becomes the organization default or serves one project. You can change this later from a project's Infrastructure tab.
5. Run the test job
The last step submits a tiny job (public alpine image) that echoes a line and writes one artifact. Seeing it succeed proves the whole path: queue → your machine → results back in Marathoon.
Connect your storage
Settings → Infrastructure → Connect infrastructure → Storage takes an S3 bucket, a MinIO endpoint or an Azure Blob container plus its credentials. Credentials are validated, stored encrypted (or in your Key Vault when the deployment uses one) and never displayed again. The form also generates a least-privilege policy limited to the bucket — create a dedicated identity with it instead of reusing an admin key.
By default files are streamed through the Marathoon API when a runner downloads inputs or uploads outputs; Marathoon does not retain a copy. Tick Runners talk to this storage directly and the runner instead moves files straight to and from your bucket with short-lived pre-authorized URLs — the content never transits through Marathoon (the runner falls back to the API for any file it cannot reach directly, and says so in the job log).
Prefer no shared secret at all: choose Cross-account role (AWS — a role Marathoon assumes, pinned to a per-provider External ID in its trust policy) or Managed identity (Azure — a role assignment on your container for Marathoon's identity). The form generates the trust policy or the az role assignment command; revoking is a change on your side.
Cloud VM in one shot
The wizard's Cloud VM tab packages the same token as a cloud-init user-data document (works on AWS, Azure, GCP, Hetzner, OVH… any image with cloud-init) and as Terraform for one EC2 instance or one Azure VM, egress-only, with no inbound rule. Because the token is single-use and lives 15 minutes, apply right away and never bake the document into a machine image.
Private image registries
If your job images live in a private registry, edit the Connected Runner provider (Settings → Infrastructure, Providers tab) and fill in Private image registry: host (blank for Docker Hub), username, password or access token. The credential is stored encrypted and handed to the enrolled runner only when it claims a job whose image is on that registry — public images keep pulling anonymously and the secret never shows up in an API response or a log. Use a read-only token scoped to the images you run.
Managing machines
Settings → Connected machines lists every machine running the agent — file watchers and runners alike. For a runner it shows live status (online, Docker reachable), the providers whose jobs route to it, the API key it uses, and enrollment tokens still waiting for a machine (cancel one there if it leaked). Revoke cuts a runner off in one action — keys deactivated, provider bindings removed, record deleted — and the machine is refused on its next poll. To reconnect it, enrol it again.
Security model
- The runner's API key is limited to the runner endpoints (claim, heartbeat, report, file staging) and bound to that machine's agent id. It cannot submit jobs or read pipelines.
- Revoke a runner from Settings → Connected machines: key, routing and record are cut together, and it stops receiving work on its next poll. The same action revokes a file agent.
- The runner never listens on the network. Its local admin API is bound to
127.0.0.1. - Prefer a dedicated machine, rootless Docker or a socket proxy for the Docker engine you hand to the runner: job containers run with the privileges of that engine.
Troubleshooting
- Token expired / already used — generate a new one from the wizard; each token is single use.
- "Docker is not reachable" — see step 3;
docker logs marathoon-runnerorjournalctl -u marathoon-agent -fshow the probe result. - Job stays queued — the runner must be online and the project must route to it (step 4). "Test connection" on the provider tells you which is missing.
- Empty outputs — the staging directory is not shared with the daemon at the same path (Docker) or not shared in Docker Desktop (Windows).
- Different Docker endpoint — set
DOCKER_HOST(e.g.tcp://10.0.0.5:2375or a rootless socket) in the container environment or in/etc/marathoon-agent/environment.