|
| 1 | +--- |
| 2 | +title: How to run GitHub Copilot coding agent on ARC with Docker-in-Docker |
| 3 | +description: Configure Actions Runner Controller with Docker-in-Docker so GitHub Copilot coding agent can run on self-hosted Kubernetes runners. |
| 4 | +sidebar: |
| 5 | + order: 440 |
| 6 | +--- |
| 7 | + |
| 8 | +Use this guide to run GitHub Copilot coding agent on an [Actions Runner Controller (ARC)](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/about-actions-runner-controller) runner scale set with Docker-in-Docker (DinD). |
| 9 | + |
| 10 | +## Prerequisites |
| 11 | + |
| 12 | +Before starting, confirm you have a Kubernetes cluster, `helm` and `kubectl` installed, and credentials for runner registration (a GitHub PAT or GitHub App credentials). |
| 13 | + |
| 14 | +> [!IMPORTANT] |
| 15 | +> DinD (`containerMode.type="dind"`) is required for GitHub Copilot coding agent on ARC. Kubernetes mode (`containerMode.type="kubernetes"`) is not supported for this setup. |
| 16 | +
|
| 17 | +## 1. Install the ARC controller |
| 18 | + |
| 19 | +```bash |
| 20 | +helm install arc \ |
| 21 | + --namespace "arc-system" --create-namespace \ |
| 22 | + oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller |
| 23 | +``` |
| 24 | + |
| 25 | +## 2. Create the runner namespace and auth secret |
| 26 | + |
| 27 | +Create the namespace and a Kubernetes secret with your runner registration credentials. You can use either a GitHub PAT or GitHub App credentials: |
| 28 | + |
| 29 | +```bash |
| 30 | +kubectl create ns arc-runners |
| 31 | + |
| 32 | +# Option A: Personal access token |
| 33 | +kubectl create secret generic arc-runner-secret \ |
| 34 | + --namespace=arc-runners \ |
| 35 | + --from-literal=github_token=<YOUR_PAT> |
| 36 | + |
| 37 | +# Option B: GitHub App (recommended for production) |
| 38 | +kubectl create secret generic arc-runner-secret \ |
| 39 | + --namespace=arc-runners \ |
| 40 | + --from-literal=github_app_id=<APP_ID> \ |
| 41 | + --from-literal=github_app_installation_id=<INSTALL_ID> \ |
| 42 | + --from-literal=github_app_private_key=<PRIVATE_KEY> |
| 43 | +``` |
| 44 | + |
| 45 | +See [Authenticating to the GitHub API](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/authenticating-to-the-github-api) for details on each option. |
| 46 | + |
| 47 | +## 3. Install a runner scale set in DinD mode |
| 48 | + |
| 49 | +```bash |
| 50 | +helm install "arc-runner-set" \ |
| 51 | + --namespace "arc-runners" --create-namespace \ |
| 52 | + --set githubConfigUrl="https://github.com/<OWNER>/<REPO>" \ |
| 53 | + --set githubConfigSecret="arc-runner-secret" \ |
| 54 | + --set containerMode.type="dind" \ |
| 55 | + --set-json 'template.spec.containers=[{ |
| 56 | + "name": "runner", |
| 57 | + "image": "ghcr.io/actions/actions-runner:latest", |
| 58 | + "command": ["/home/runner/run.sh"], |
| 59 | + "securityContext": { |
| 60 | + "capabilities": { |
| 61 | + "add": ["NET_ADMIN"] |
| 62 | + } |
| 63 | + } |
| 64 | + }]' \ |
| 65 | + oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set |
| 66 | +``` |
| 67 | + |
| 68 | +`NET_ADMIN` is required on the **runner container** so AWF can apply host-level `iptables` rules to the `DOCKER-USER` chain for egress filtering. |
| 69 | + |
| 70 | +When `containerMode.type="dind"` is enabled, ARC configures the DinD sidecar in privileged mode by default so the Docker daemon can run. If you use a custom pod template, ensure you do not remove that privileged setting. |
| 71 | + |
| 72 | +## 4. Verify the runner is online |
| 73 | + |
| 74 | +Open `https://github.com/<OWNER>/<REPO>/settings/actions/runners` (or the organization-level runners page) and confirm the `arc-runner-set` runner is online. |
| 75 | + |
| 76 | +## 5. Target the runner set from a workflow |
| 77 | + |
| 78 | +Set your workflow frontmatter to use the runner scale set label and ARC DinD topology: |
| 79 | + |
| 80 | +```aw |
| 81 | +--- |
| 82 | +on: issues |
| 83 | +runs-on: arc-runner-set |
| 84 | +runner: |
| 85 | + topology: arc-dind |
| 86 | +--- |
| 87 | +``` |
| 88 | + |
| 89 | +`runner.topology: arc-dind` is required so compiled workflows enable ARC DinD split-filesystem handling (a shared runner/daemon workspace root, Docker-daemon-visible mount paths, and ARC-specific sandbox setup). No other sandbox or network settings are needed — the defaults handle everything else. |
| 90 | + |
| 91 | +After editing the frontmatter, recompile the lock file: |
| 92 | + |
| 93 | +```bash |
| 94 | +gh aw compile |
| 95 | +``` |
| 96 | + |
| 97 | +Commit both the `.md` workflow file and the generated `.lock.yml` file. |
| 98 | + |
| 99 | +## 6. How it works |
| 100 | + |
| 101 | +When compiled workflows detect a `tcp://` value in `DOCKER_HOST` (set automatically by ARC DinD), a runtime probe activates ARC DinD handling: |
| 102 | + |
| 103 | +- **Sysroot staging** — system binaries (`/usr`, `/lib`, `/bin`, `/sbin`) are copied into a Docker named volume so the Docker daemon can provide them to the agent container without bind-mounting the runner's filesystem. |
| 104 | +- **Workspace mount** — the checked-out repository at `GITHUB_WORKSPACE` is explicitly mounted into the agent container. Both runner and daemon can see it because ARC DinD shares the `/home/runner/_work/` volume. |
| 105 | +- **Chroot identity** — the runner's UID/GID and home directory are patched into the AWF config so the agent runs with the correct identity inside the chroot. |
| 106 | +- **Artifact consolidation** — agent output files are consolidated under `${{ runner.temp }}/gh-aw/` before upload so downstream jobs (detection, safe-outputs) can find them. |
| 107 | + |
| 108 | +## 7. Required versions |
| 109 | + |
| 110 | +Use versions at or above these minimums: |
| 111 | + |
| 112 | +| Component | Minimum version | Why | |
| 113 | +| --- | --- | --- | |
| 114 | +| `gh-aw` | `v0.82.5` | Includes ARC DinD workspace and detection fixes. | |
| 115 | +| AWF (`agentic-workflow-firewall`) | `v0.27.22` | Includes DinD squid log permission fixes. | |
| 116 | + |
| 117 | +## Required and optional configuration |
| 118 | + |
| 119 | +| Item | Required? | Notes | |
| 120 | +| --- | --- | --- | |
| 121 | +| DinD container mode | **Yes** | GitHub Copilot coding agent needs a Docker daemon in the runner pod. | |
| 122 | +| `NET_ADMIN` capability | **Yes** | Required on the runner container so AWF can manage host-level `DOCKER-USER` `iptables` rules. | |
| 123 | +| `ghcr.io/actions/actions-runner:latest` | Recommended | Use the official runner image, or a compatible custom image with equivalent runner requirements. | |
| 124 | +| Runner user | **Yes** | Non-root runner users are supported, but `sudo` must remain available on the runner host for AWF setup operations. | |
| 125 | +| DinD sidecar privilege | **Yes** | ARC DinD mode configures a privileged sidecar for Docker daemon operation. | |
| 126 | +| Shared work volume (`/home/runner/_work`) | **Yes** | Runner and Docker daemon share this volume in ARC DinD mode, so workspace mounts work without host path translation. | |
| 127 | +| Specific Kubernetes distribution | **No** | Any conformant cluster works (for example minikube, EKS, AKS, or GKE). | |
| 128 | +| Specific namespace names | **No** | `arc-system` and `arc-runners` are conventions only. | |
| 129 | + |
| 130 | +## Upgrading from manual workarounds |
| 131 | + |
| 132 | +If you previously used custom bootstrap actions, copilot shims, `/etc` pre-seeding, XDG environment overrides, or manual `DOCKER_HOST` / `MCP_GATEWAY_DOMAIN` settings to run on ARC DinD, remove them when adopting `runner.topology: arc-dind`. The compiler now handles all of these automatically. Leftover workarounds may conflict with the generated workflow steps. |
| 133 | + |
| 134 | +To migrate: |
| 135 | + |
| 136 | +1. Remove any `pre-agent-steps`, `resources`, or `safe-outputs.threat-detection.steps` blocks that were workarounds for ARC DinD. |
| 137 | +2. Remove manual `engine.env` overrides for `XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, `MCP_GATEWAY_DOMAIN`, `MCP_GATEWAY_PORT`, and `DOCKER_HOST`. |
| 138 | +3. Remove `sandbox.agent.mounts` entries that staged files for the DinD daemon. |
| 139 | +4. Add `runner.topology: arc-dind` to frontmatter. |
| 140 | +5. Run `gh aw compile` and commit the updated lock file. |
| 141 | + |
| 142 | +## Known limitations |
| 143 | + |
| 144 | +- **`allowPrivilegeEscalation: false` is not supported.** The Copilot CLI install script uses `sudo`. Clusters that enforce `no-new-privileges` via PodSecurity Admission or OPA policies will fail at the install step. |
| 145 | +- **MCP gateway Docker socket access** — on runners where `DOCKER_HOST` is a TCP endpoint and no Unix socket exists at `/var/run/docker.sock`, the MCP gateway may fail to connect to the Docker daemon (`Docker daemon is not accessible`). As a workaround, expose the DinD sidecar's Unix socket on the runner container at `/var/run/docker.sock` via a shared volume or symlink. See [#44251](https://github.com/github/gh-aw/issues/44251) for tracking. |
| 146 | + |
| 147 | +## Troubleshooting |
| 148 | + |
| 149 | +### Agent reports empty workspace |
| 150 | + |
| 151 | +The agent sees no files and exits with a no-op message. This was fixed in gh-aw v0.82.5. Upgrade and recompile: |
| 152 | + |
| 153 | +```bash |
| 154 | +gh aw upgrade |
| 155 | +gh aw compile |
| 156 | +``` |
| 157 | + |
| 158 | +### Detection job fails with `spawn /usr/local/bin/copilot ENOENT` |
| 159 | + |
| 160 | +The threat-detection job can't find the Copilot binary. This was fixed in gh-aw v0.82.5 ([#44445](https://github.com/github/gh-aw/pull/44445)). The fix is the same — upgrade and recompile. |
| 161 | + |
| 162 | +### `sudo: The "no new privileges" flag is set` |
| 163 | + |
| 164 | +The runner pod's security context has `allowPrivilegeEscalation: false`. Remove that constraint or adjust your PodSecurity policy to allow privilege escalation in the runner container. |
| 165 | + |
| 166 | +### `Docker daemon is not accessible` in MCP gateway |
| 167 | + |
| 168 | +The MCP gateway can't reach the Docker socket. Ensure a Unix socket is available at `/var/run/docker.sock` on the runner container. For DinD setups where the daemon only exposes a TCP endpoint, share the sidecar's socket file via a volume: |
| 169 | + |
| 170 | +```yaml |
| 171 | +# In your custom runner pod template |
| 172 | +volumes: |
| 173 | + - name: dind-sock |
| 174 | + emptyDir: {} |
| 175 | +# Mount in both runner and DinD sidecar containers |
| 176 | +volumeMounts: |
| 177 | + - name: dind-sock |
| 178 | + mountPath: /var/run |
| 179 | +``` |
| 180 | +
|
| 181 | +## Related documentation |
| 182 | +
|
| 183 | +- [Self-Hosted Runners](/gh-aw/reference/self-hosted-runners/) |
| 184 | +- [ARC Helm charts](https://github.com/actions/actions-runner-controller/tree/master/charts) |
0 commit comments