Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export default defineConfig({
{ label: 'Upgrading Workflows', link: '/guides/upgrading/' },
{ label: 'Using MCPs', link: '/guides/mcps/' },
{ label: 'Network Configuration', link: '/guides/network-configuration/' },
{ label: 'ARC DinD for Copilot Coding Agent', link: '/guides/arc-dind-copilot-agent/' },
{ label: 'OpenTelemetry', link: '/guides/open-telemetry/' },
{ label: 'GitHub Actions Primer', link: '/guides/github-actions-primer/' },
{ label: 'Using at Scale in Organizations', link: '/guides/using-at-scale/' },
Expand Down
184 changes: 184 additions & 0 deletions docs/src/content/docs/guides/arc-dind-copilot-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
title: How to run GitHub Copilot coding agent on ARC with Docker-in-Docker
description: Configure Actions Runner Controller with Docker-in-Docker so GitHub Copilot coding agent can run on self-hosted Kubernetes runners.
sidebar:
order: 440
---

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).

## Prerequisites

Before starting, confirm you have a Kubernetes cluster, `helm` and `kubectl` installed, and credentials for runner registration (a GitHub PAT or GitHub App credentials).

> [!IMPORTANT]
> DinD (`containerMode.type="dind"`) is required for GitHub Copilot coding agent on ARC. Kubernetes mode (`containerMode.type="kubernetes"`) is not supported for this setup.

## 1. Install the ARC controller

```bash
helm install arc \
--namespace "arc-system" --create-namespace \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
```

## 2. Create the runner namespace and auth secret

Create the namespace and a Kubernetes secret with your runner registration credentials. You can use either a GitHub PAT or GitHub App credentials:

```bash
kubectl create ns arc-runners

# Option A: Personal access token
kubectl create secret generic arc-runner-secret \
--namespace=arc-runners \
--from-literal=github_token=<YOUR_PAT>

# Option B: GitHub App (recommended for production)
kubectl create secret generic arc-runner-secret \
--namespace=arc-runners \
--from-literal=github_app_id=<APP_ID> \
--from-literal=github_app_installation_id=<INSTALL_ID> \
--from-literal=github_app_private_key=<PRIVATE_KEY>
```

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.

## 3. Install a runner scale set in DinD mode

```bash
helm install "arc-runner-set" \
--namespace "arc-runners" --create-namespace \
--set githubConfigUrl="https://github.com/<OWNER>/<REPO>" \
--set githubConfigSecret="arc-runner-secret" \
--set containerMode.type="dind" \
--set-json 'template.spec.containers=[{
"name": "runner",
"image": "ghcr.io/actions/actions-runner:latest",
"command": ["/home/runner/run.sh"],
"securityContext": {
"capabilities": {
"add": ["NET_ADMIN"]
}
}
}]' \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
Comment on lines +50 to +65
```

`NET_ADMIN` is required on the **runner container** so AWF can apply host-level `iptables` rules to the `DOCKER-USER` chain for egress filtering.

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.

## 4. Verify the runner is online

Open `https://github.com/<OWNER>/<REPO>/settings/actions/runners` (or the organization-level runners page) and confirm the `arc-runner-set` runner is online.

## 5. Target the runner set from a workflow

Set your workflow frontmatter to use the runner scale set label and ARC DinD topology:

```aw
---
on: issues
runs-on: arc-runner-set
runner:
topology: arc-dind
---
```

`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.

After editing the frontmatter, recompile the lock file:

```bash
gh aw compile
```

Commit both the `.md` workflow file and the generated `.lock.yml` file.

## 6. How it works

When compiled workflows detect a `tcp://` value in `DOCKER_HOST` (set automatically by ARC DinD), a runtime probe activates ARC DinD handling:

- **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.
- **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.
- **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.
- **Artifact consolidation** — agent output files are consolidated under `${{ runner.temp }}/gh-aw/` before upload so downstream jobs (detection, safe-outputs) can find them.

## 7. Required versions

Use versions at or above these minimums:

| Component | Minimum version | Why |
| --- | --- | --- |
| `gh-aw` | `v0.82.5` | Includes ARC DinD workspace and detection fixes. |
| AWF (`agentic-workflow-firewall`) | `v0.27.22` | Includes DinD squid log permission fixes. |

## Required and optional configuration

| Item | Required? | Notes |
| --- | --- | --- |
| DinD container mode | **Yes** | GitHub Copilot coding agent needs a Docker daemon in the runner pod. |
| `NET_ADMIN` capability | **Yes** | Required on the runner container so AWF can manage host-level `DOCKER-USER` `iptables` rules. |
| `ghcr.io/actions/actions-runner:latest` | Recommended | Use the official runner image, or a compatible custom image with equivalent runner requirements. |
| Runner user | **Yes** | Non-root runner users are supported, but `sudo` must remain available on the runner host for AWF setup operations. |
| DinD sidecar privilege | **Yes** | ARC DinD mode configures a privileged sidecar for Docker daemon operation. |
| 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. |
| Specific Kubernetes distribution | **No** | Any conformant cluster works (for example minikube, EKS, AKS, or GKE). |
| Specific namespace names | **No** | `arc-system` and `arc-runners` are conventions only. |

## Upgrading from manual workarounds

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.

To migrate:

1. Remove any `pre-agent-steps`, `resources`, or `safe-outputs.threat-detection.steps` blocks that were workarounds for ARC DinD.
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`.
3. Remove `sandbox.agent.mounts` entries that staged files for the DinD daemon.
4. Add `runner.topology: arc-dind` to frontmatter.
5. Run `gh aw compile` and commit the updated lock file.

## Known limitations

- **`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.
- **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.

## Troubleshooting

### Agent reports empty workspace

The agent sees no files and exits with a no-op message. This was fixed in gh-aw v0.82.5. Upgrade and recompile:

```bash
gh aw upgrade
gh aw compile
```

### Detection job fails with `spawn /usr/local/bin/copilot ENOENT`

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.

### `sudo: The "no new privileges" flag is set`

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.

### `Docker daemon is not accessible` in MCP gateway

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:

```yaml
# In your custom runner pod template
volumes:
- name: dind-sock
emptyDir: {}
# Mount in both runner and DinD sidecar containers
volumeMounts:
- name: dind-sock
mountPath: /var/run
```

## Related documentation

- [Self-Hosted Runners](/gh-aw/reference/self-hosted-runners/)
- [ARC Helm charts](https://github.com/actions/actions-runner-controller/tree/master/charts)
14 changes: 9 additions & 5 deletions docs/src/content/docs/reference/self-hosted-runners.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ For these reasons, a non-sudo mode is not supported, including ARC configuration

## ARC with Docker-in-Docker (DinD)

Actions Runner Controller (ARC) deployments that use a Docker-in-Docker sidecar split the runner container and the Docker daemon container across separate filesystems, so bind mounts constructed from the runner's perspective fail inside the daemon.
For a complete ARC DinD setup walkthrough for GitHub Copilot coding agent, see [How to run GitHub Copilot coding agent on ARC with Docker-in-Docker](/gh-aw/guides/arc-dind-copilot-agent/).

`gh aw compile` emits a runtime probe in generated workflows that inspects `DOCKER_HOST` and appends `--docker-host-path-prefix /tmp/gh-aw` to the AWF invocation when the value matches `tcp://localhost:<port>` or `tcp://127.0.0.1:<port>`. No workflow-level configuration is required.
Actions Runner Controller (ARC) deployments that use a Docker-in-Docker sidecar split the runner container and the Docker daemon container across separate filesystems.

The probe is gated on AWF `v0.25.43` or newer. Workflows pinned to an older AWF version, or running on GitHub-hosted runners (where `DOCKER_HOST` is unset or points at a Unix socket), are unaffected.
Set `runner.topology: arc-dind` in workflow frontmatter for this environment.
Compiled workflows emit a runtime probe that inspects `DOCKER_HOST`.
Any `tcp://` endpoint (for example `tcp://localhost:2375`, `tcp://dind:2375`, or `tcp://172.30.0.5:2375`) is treated as ARC DinD, so ensure `DOCKER_HOST` points to the DinD daemon for that runner pod.

With ARC DinD handling enabled, AWF receives `--docker-host`, shared-work sysroot staging is applied, and chroot config patching is enabled. The runtime no longer uses `--docker-host-path-prefix`.

## runs-on formats

Expand Down Expand Up @@ -171,8 +175,8 @@ A working Docker daemon is required. The MCP gateway and sandbox run as containe

### Filesystem

- **Use `RUNNER_TEMP` for transient state.** Put sandbox state, tool downloads, and intermediate outputs in `$RUNNER_TEMP`, which is cleaned between jobs. On shared runners, avoid writing arbitrary workflow data to `/tmp` because it can persist across jobs. The `/tmp/gh-aw` prefix is reserved for gh-aw/AWF ARC DinD path rewriting. `actions/setup` resets `/tmp/gh-aw` at job start, and your normal runner `/tmp` cleanup policy should handle stale data from interrupted jobs.
- **No root or sudo assumption.** The runner user may not have root or `sudo` access (except for the initial iptables setup, which requires `sudo`). Tool installs, file operations, and sandbox setup should work as the unprivileged runner user.
- **Use `RUNNER_TEMP` for transient state.** Put sandbox state, tool downloads, and intermediate outputs in `$RUNNER_TEMP`, which is cleaned between jobs. On shared runners, avoid writing arbitrary workflow data to `/tmp` because it can persist across jobs.
- **No root assumption.** Tool installs, file operations, and sandbox setup should run as the unprivileged runner user, but host-level AWF setup requires `sudo` support on the runner host.
- **No global installs.** Do not install packages to `/usr/local/`, `/opt/hostedtoolcache/`, or other system-wide paths. These may be read-only, shared across runners, or bind-mounted read-only inside the sandbox. Use job-scoped writable locations instead.
- **No hardcoded `HOME` paths.** The runner's home directory may not be `/home/runner`. Use `$HOME` or `$RUNNER_TEMP` instead of hardcoded paths.

Expand Down
Loading