InfraDots logo
Documentation

Self-hosted Runners

Run InfraDots in your own environment: the agent runner reviews pull requests and implements infrastructure changes with your own model account, and the executor runs your Terraform, OpenTofu and Terragrunt plans and applies. Both register with a worker pool and connect out to InfraDots over HTTPS. Nothing needs to reach into your network.

How it works

  1. You create a pool in InfraDots and start runners with its registration token.

  2. Each runner registers, then polls InfraDots for work routed to its pool.

  3. For each run:

    • The agent runner gets a token scoped to that one run and a short-lived git credential for that run's repository. It clones the repository, calls your model provider, and opens or comments on the pull request through InfraDots.
    • The executor downloads the run's configuration from InfraDots and runs Terraform, OpenTofu or Terragrunt with your workspace's variables.

    Then the runner reports the result.

  4. Results, logs and history appear in InfraDots as for any other run.

Each runner handles one run or job at a time. Run more replicas for more throughput.

Requirements

  • Kubernetes 1.25+ (for the Helm chart), or any Docker host. The images are linux/amd64 and linux/arm64, so they also run under Docker Desktop, Colima or OrbStack on Intel and Apple Silicon Macs.
  • Outbound HTTPS to the destinations under Network.
  • For the agent runner: a model account (see Your model account).

1. Create the pools

In Organization settings → Worker Pools, create a pool of kind agent, kind executor, or both, and copy each registration token. See Worker Pools.

2a. Install on Kubernetes (Helm)

helm repo add infradots https://infra-dots.github.io/helm-charts
helm repo update

kubectl create namespace infradots
kubectl -n infradots create secret generic infradots-agent-pool --from-literal=REGISTRATION_TOKEN=<agent pool token>
kubectl -n infradots create secret generic anthropic --from-literal=ANTHROPIC_API_KEY=<your key>

helm -n infradots install runners infradots/infradots-runner \
  --set agent.registration.existingSecret=infradots-agent-pool \
  --set agent.model.existingSecret=anthropic

To run the executor too, create its pool's Secret and add:

  --set executor.enabled=true \
  --set executor.registration.existingSecret=infradots-executor-pool

The chart is hardened by default:

  • non-root, with a read-only root filesystem and every Linux capability dropped
  • no Kubernetes API token mounted
  • size-capped scratch space

It also supports workload identity, an egress proxy and an optional NetworkPolicy. Ready-made values for Bedrock, Vertex, an egress proxy and more are in the chart's examples, and every value is in the chart README.

Check the agent runner's setup:

kubectl -n infradots exec deploy/runners-infradots-runner-agent -- idp-agent doctor

2b. Run with Docker (VMs and on-prem)

Agent runner:

docker run -d --name infradots-agent --restart unless-stopped \
  -e IDP_URL=https://app.infradots.com \
  -e REGISTRATION_TOKEN=<agent pool token> \
  -e ANTHROPIC_API_KEY=<your key> \
  public.ecr.aws/e5i7i1j1/idp-agent:latest

Executor:

docker run -d --name infradots-executor --restart unless-stopped \
  -e REGISTRATION_TOKEN=<executor pool token> \
  public.ecr.aws/e5i7i1j1/idp-executor:latest

Check the agent runner's setup with docker exec infradots-agent idp-agent doctor.

With Docker Compose: keep the tokens in an .env file next to compose.yaml.

services:
  agent:
    image: public.ecr.aws/e5i7i1j1/idp-agent:latest
    restart: unless-stopped
    environment:
      IDP_URL: https://app.infradots.com
      REGISTRATION_TOKEN: ${AGENT_POOL_TOKEN}
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
    stop_grace_period: 60s

  executor:
    image: public.ecr.aws/e5i7i1j1/idp-executor:latest
    restart: unless-stopped
    environment:
      REGISTRATION_TOKEN: ${EXECUTOR_POOL_TOKEN}
    # On shutdown the executor finishes its current job first: allow for your longest apply.
    stop_grace_period: 1h

As a systemd service: to run the agent runner on boot, put its variables in /etc/infradots/agent.env (IDP_URL=…, REGISTRATION_TOKEN=…, ANTHROPIC_API_KEY=…, mode 0600) and add /etc/systemd/system/infradots-agent.service:

[Unit]
Description=InfraDots agent runner
After=docker.service
Requires=docker.service

[Service]
ExecStartPre=-/usr/bin/docker rm -f infradots-agent
ExecStart=/usr/bin/docker run --rm --name infradots-agent --env-file /etc/infradots/agent.env public.ecr.aws/e5i7i1j1/idp-agent:latest
ExecStop=/usr/bin/docker stop -t 60 infradots-agent
Restart=always

[Install]
WantedBy=multi-user.target

Then systemctl daemon-reload && systemctl enable --now infradots-agent. For the executor, use its image and token, and raise the stop timeout (docker stop -t 3600, and TimeoutStopSec=3700 in [Service]) so a running apply can finish.

💡 Tip

[!tip] Pin a version (idp-agent:0.5.0, idp-executor:1.9.0) instead of latest in production, and upgrade deliberately.

Your model account

InfraDots chooses which Claude model each agent uses, and your runner sends the call through your own account. Prompts, tools and results are the same whichever provider you use.

ProviderMODEL_PROVIDERCredentialsMODEL_MAP
Anthropic APIanthropic (default)ANTHROPIC_API_KEYnot needed
AWS Bedrockbedrockthe AWS credential chain (IRSA, EKS Pod Identity, an instance profile, or AWS_* variables), plus AWS_REGIONrequired
Google Vertex AIvertexApplication Default Credentials or GKE Workload Identity, plus CLOUD_ML_REGION and ANTHROPIC_VERTEX_PROJECT_IDrequired
Azure AI FoundryfoundryANTHROPIC_FOUNDRY_API_KEY and ANTHROPIC_FOUNDRY_RESOURCErequired

MODEL_MAP is JSON that maps InfraDots' model names to your provider's model IDs. For example, for Bedrock:

{
  "claude-opus-5": "eu.anthropic.claude-opus-5-v1:0",
  "claude-sonnet-5": "eu.anthropic.claude-sonnet-5-v1:0"
}

With the Helm chart, set agent.model.provider and agent.model.map instead.

Map every model InfraDots uses. idp-agent doctor sends a one-token request to each mapped model. A run that needs a model the map lacks stops before cloning anything, and its message names the model to add.

Permissions. On Bedrock, the runner's role needs bedrock:InvokeModel and bedrock:InvokeModelWithResponseStream on the mapped models or inference profiles. On Vertex AI, its service account needs roles/aiplatform.user in the project. Enable the Claude models in your account's model catalog first.

ℹ️ Info

[!info] Runs on your own model account don't count against your plan's AI interactions. InfraDots still records their token usage, so you can see it.

Configuration

Both runners are configured through environment variables. The Helm chart sets them for you from its values.

Agent runner (idp-agent)

VariableDefault
IDP_URL(required)Your InfraDots URL, e.g. https://app.infradots.com
REGISTRATION_TOKEN(required)The agent pool's token
RUNNER_NAMEhostnameHow the runner shows in its pool
MODEL_PROVIDER / MODEL_MAPanthropic / {}See Your model account
WORK_DIR$TMPDIR/idp-agentWhere runs clone. Must be writable and allow running programs (not noexec)
TERRAFORM_RELEASES_URL / OPENTOFU_RELEASES_URLHashiCorp / GitHubMirrors for the fmt check's downloads
HTTPS_PROXY / NO_PROXYEgress proxy
LOG_LEVELINFO

Executor (idp-executor)

VariableDefault
REGISTRATION_TOKEN(required)The executor pool's token
REGISTRATION_HOSTapp.infradots.comYour InfraDots host
EXECUTABLE_CACHE_ENABLEDfalseKeep downloaded Terraform/OpenTofu binaries between jobs
TERRAFORM_RELEASES_URL / TERRAGRUNT_RELEASES_URLHashiCorp / GitHubMirrors for binary downloads
HTTPS_PROXY / NO_PROXYEgress proxy
LOG_LEVELINFO

Network

The runners make outbound connections only:

DestinationRunnerWhy
app.infradots.com (your InfraDots URL), port 443bothRegistration, receiving work, reporting results
Your git host: github.com, gitlab.com, bitbucket.org or your ownagentCloning, and pushing its branches
Git hosts your Terraform modules come from, if anyexecutorterraform init of git:: module sources
Your model provider's APIagentModel calls
releases.hashicorp.com, api.github.com and GitHub release downloadsbothTerraform/OpenTofu/Terragrunt binaries (can be mirrored)
Terraform provider and module registriesexecutorterraform init

Kubernetes NetworkPolicy can't filter by hostname. To allow only these destinations, route the runners through an egress proxy (HTTPS_PROXY), or use your CNI's FQDN policies.

Security

  • Per-run credentials. For each agent run, InfraDots issues the runner a token scoped to that run, and a git credential for that run's repository only, valid for the run. The runner holds no long-lived VCS secret. The executor doesn't need git access to your repositories: it downloads each run's configuration from InfraDots.
  • The registration token is good only for its own pool, and only for that pool's kind of work. Rotate it on the pool if it may have leaked; see Rotating a token.
  • Clean workspace per run. Each run works in a fresh directory, which is removed or wiped when the run ends. The agent runner passes git credentials to git at run time, never writing them into the clone or its config.
  • Your model calls stay in your account. The agent runner calls your provider directly; prompts and code don't pass through an InfraDots model account.

Upgrades

Runners and InfraDots are versioned separately. InfraDots supports the previous runner version, and a runner too old for the work InfraDots sends is marked needs upgrade on its pool, so you see it before runs wait on it.

  • Helm: helm repo update && helm -n infradots upgrade runners infradots/infradots-runner --reuse-values
  • Docker: pull the new image tag and recreate the container.

On shutdown the agent runner reports the run it was carrying out as interrupted, and the executor finishes its current job before exiting.

Troubleshooting

idp-agent doctor checks everything the agent runner needs from where it runs, and says what to fix:

ok    configuration: runner runners-infradots-runner-agent-5d8f7-x2k4p, models via bedrock
ok    work directory: /work is writable and can run programs
ok    git: git version 2.47.3
ok    platform: https://app.infradots.com accepted the registration token
ok    model anthropic:claude-opus-5 via bedrock: answered
ok    model anthropic:claude-sonnet-5 via bedrock: answered
All checks passed.
SymptomCheck
The runner doesn't appear in its poolIts log: it retries registration while InfraDots is unreachable. Check IDP_URL, egress to it, and that the token is the pool's current one
doctor fails on the work directoryThe volume is mounted noexec, or read-only. Use a writable, exec-allowed path for WORK_DIR
doctor fails on a modelThe provider's own error is shown: credentials, region, model access, or a wrong ID in MODEL_MAP
Runs wait, then failThe message says whether no runner was online, the runners need an upgrade, or all were busy. See Worker Pools