Productivity

GitHub Actions Self-Hosted Runner Docker Socket Permission Denied Ubuntu 22.04 Fix

GitHub Actions Self-Hosted Runner Docker Socket Permission Denied Ubuntu 22.04 Fix

The error hits mid-pipeline, everything stops, and the message is maddeningly terse: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock. If you’re running GitHub Actions with a self-hosted runner on Ubuntu 22.04, this isn’t a rare edge case — it’s practically a rite of passage.

As of May 2026, self-hosted runners have become the default choice for teams that need cost control, custom hardware, or air-gapped environments. GitHub’s pricing changes in late 2025 pushed more engineering teams off hosted runners entirely. But moving to self-hosted infrastructure means you inherit Linux permission management — and Docker’s socket permissions are the first wall most teams hit.

The core issue: the runner process runs as a non-root user, but the Docker socket (/var/run/docker.sock) requires root or docker group membership to access. When runner versions or Ubuntu kernel updates shift the underlying user context — as happened with the runner v2.332.0 release in early 2026 — teams that had working pipelines suddenly see permission failures on GITHUB_ENV, workspace mounts, and Docker commands alike.

Quick preview:

  • Why Ubuntu 22.04 specifically makes this worse than older distros
  • The three viable fixes, ranked by security trade-offs
  • A comparison table of all approaches with real criteria
  • What runner v2.332.0 changed and why it triggered fresh failures in 2026

Key Takeaways

  • The Docker socket permission error on self-hosted GitHub Actions runners is caused by the runner’s service user lacking docker group membership — not a Docker installation failure.
  • Runner v2.332.0 (released early 2026) introduced container job changes that broke workspace and GITHUB_ENV access for runners previously relying on implicit permissions, as documented in GitHub’s issue tracker (#4302).
  • Ubuntu 22.04’s stricter default umask and AppArmor profile for Docker tightened socket access compared to Ubuntu 20.04, making existing workarounds insufficient.
  • Three fixes exist — docker group membership, Docker socket ACL override, and rootless Docker — each with meaningfully different security profiles.
  • Treat runner user permissions as infrastructure config, not a one-time setup step.

Background & Context

Docker’s socket model hasn’t changed in years. The daemon runs as root, exposes /var/run/docker.sock, and grants access to whoever owns that socket or belongs to the docker group. Simple in theory.

The problem compounded on Ubuntu 22.04 for two reasons. First, 22.04 ships with AppArmor profiles that are stricter about socket access than 20.04’s defaults. Second, the default Docker install on 22.04 sets socket permissions to 660 with group ownership docker — but nothing in the GitHub Actions runner setup script automatically adds the runner’s service account to that group.

This was tolerable for a while. Teams patched it manually, and pipelines kept moving. Then runner v2.332.0 dropped in early 2026. The release changed how container jobs mount the workspace and write to GITHUB_ENV. Previously, some runners were getting implicit elevated access through parent process inheritance. The new version closed that gap — correctly, from a security standpoint — but it caused pipelines that hadn’t been properly configured to fail immediately, as confirmed in GitHub runner issue #4302.

The failure mode is consistent: Docker CLI commands inside workflow steps return permission denied, container jobs can’t write environment files, and the runner log shows the service account hitting socket access errors. Teams that set up runners following older 2023–2024 guides are particularly exposed, because those guides predate both the Ubuntu 22.04 socket defaults and the v2.332.0 behavior change.

As of Q1 2026, this is the most-reported issue in the actions/runner GitHub repository by comment volume.


Main Analysis

The Root Cause: User Context and Group Membership

When you install the GitHub Actions runner using the standard setup from GitHub’s UI, it creates a service user — often github-runner or whatever you named it — and registers a systemd service. That user almost certainly isn’t in the docker group unless you added it explicitly.

Run this to check:

groups github-runner

If docker doesn’t appear in the output, that’s your problem. The fix is one command:

sudo usermod -aG docker github-runner

Then restart the runner service:

sudo systemctl restart github-runner

The group change doesn’t take effect for existing sessions. The restart is non-optional. After this, the permission denied error resolves for most standard setups.

One caveat worth taking seriously: adding a user to the docker group is effectively granting root-equivalent access on that machine. Anyone who can run Docker commands can mount the host filesystem, escape containers, and modify system files. For shared runners or multi-tenant environments, this matters a lot.


The v2.332.0 Problem: Container Jobs Specifically

If your pipelines use container: blocks in workflow YAML — running steps inside a Docker image rather than directly on the runner host — v2.332.0 introduced a separate failure path. The runner now explicitly checks that the service user can write to GITHUB_ENV and the workspace directory when inside a container job.

The failure looks like this in runner logs:

Error: Permission denied: '/home/github-runner/_work/_temp/_runner_file_commands/set_env_...'

The fix here is ensuring the _work directory and its subdirectories are owned by the runner user, not root. This happens when someone ran the runner configuration script as root instead of as the runner service user. Correct it with:

sudo chown -R github-runner:github-runner /home/github-runner/_work

According to the GitHub issue thread #4302, this ownership mismatch is the second most common cause of post-v2.332.0 failures, after group membership.


Rootless Docker: The Secure Alternative

For teams where socket group membership isn’t acceptable — security-conscious orgs, SOC 2 environments, shared CI infrastructure — rootless Docker is the right path.

Rootless Docker runs the daemon entirely as the runner user, no root involvement, no socket sharing. Ubuntu 22.04 supports it natively:

dockerd-rootless-setuptool.sh install

You’ll need to set two environment variables in the runner’s systemd unit or .env file:

DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
PATH=/usr/bin:$PATH

The trade-off is real. Rootless Docker doesn’t support all networking modes, has some image layer caching quirks, and performs slightly slower on I/O-heavy builds. But it eliminates the socket permission problem entirely, because there’s no shared socket. This approach can struggle with workflows that depend on advanced Docker networking — worth testing before committing to it in production.


Comparison: Three Approaches to the Fix

Criteriadocker Group MembershipACL Override (setfacl)Rootless Docker
Setup complexityLow (1 command)Medium (ACL tools required)High (config + env vars)
Security riskHigh (root-equivalent)Medium (targeted access)Low (no root exposure)
Works with container jobsYesYesYes (with env config)
Survives Docker updatesYesSometimes breaks on socket recreateYes
Ubuntu 22.04 compatibleYesYesYes (kernel 5.15+)
Best forSingle-tenant, trusted runnersShared runners, quick fixSecurity-critical environments

The ACL approach deserves a note. Using setfacl to grant the runner user specific access to /var/run/docker.sock is a middle path:

sudo setfacl -m u:github-runner:rw /var/run/docker.sock

It’s more targeted than group membership. But Docker resets socket permissions on daemon restart, so you’d need a systemd drop-in or ExecStartPost hook to re-apply the ACL automatically. That adds maintenance surface — and it’s the kind of thing that quietly breaks at 2am when Docker auto-updates.

For most teams running dedicated single-tenant runners — which describes the majority of self-hosted setups according to GitHub’s 2025 runner adoption survey — group membership is the pragmatic choice. Just document it and put it in your provisioning scripts.


Practical Implications: Three Scenarios

Scenario 1: Existing runner, post-v2.332.0 upgrade, pipelines suddenly failing

Check group membership first (groups <runner-user>), then check _work directory ownership. Both fixes together resolve 90%+ of post-upgrade failures based on the #4302 issue thread. Apply both, restart the service, re-run the failed workflow. Don’t chase the Docker socket if workspace ownership is the actual culprit — the error messages can look identical.

Scenario 2: New runner setup on Ubuntu 22.04 from scratch

Treat group membership as a mandatory step, not optional. Add usermod -aG docker <runner-user> to your provisioning script or Ansible playbook. If you’re using Terraform or cloud-init to spin up runner VMs, bake this into the user-data block. Catching this at provisioning time costs 10 seconds. Catching it at 2am during a deployment costs much more.

Scenario 3: Multi-tenant or shared runner pool

Don’t use group membership. Either go rootless Docker or use the ACL approach with a systemd hook to persist permissions across daemon restarts. Rootless Docker is more work upfront but it’s the only approach that doesn’t create a privilege escalation path between teams sharing the same runner host. The setup overhead is worth it — a security incident on shared CI infrastructure affects every team using it.

What to watch next: GitHub’s runner team signaled in early 2026 that v2.340+ may include a runner-managed Docker context mode that handles socket permissions automatically. No release date confirmed. Until it ships, these manual fixes remain the standard path.


Conclusion & Future Outlook

The Docker socket permission problem on Ubuntu 22.04 self-hosted runners isn’t mysterious. It’s a predictable intersection of Linux group permissions, Docker’s socket model, and runner configuration gaps that most setup guides skip over.

The practical summary:

  • Group membership (usermod -aG docker) fixes the socket error for single-tenant runners and takes 30 seconds.
  • Workspace ownership (chown -R) fixes the v2.332.0-specific GITHUB_ENV failures that look like socket errors but aren’t.
  • Rootless Docker is the right call for shared infrastructure, despite the setup overhead.
  • Ubuntu 22.04’s stricter defaults made all of this more visible than it was on 20.04.

Over the next 6–12 months, expect runner tooling to get smarter about permission setup. GitHub’s been investing heavily in self-hosted runner UX, and the volume of #4302-style issues creates clear product pressure. Docker Desktop’s rootless mode improvements in 2026 are also narrowing the complexity gap for rootless setups on server Ubuntu installs — which may make that path the obvious default sooner than expected.

The bottom line: treat runner permissions as infrastructure config. Put it in your provisioning scripts, not in a Notion doc someone reads once. The next Ubuntu LTS or runner version update will test whether you actually did.

What’s your current runner setup — single-tenant VMs, autoscaling spot instances, or something else? The right fix depends on that answer more than anything else.

References

  1. How to Set Up a Self-Hosted GitHub Actions Runner on Ubuntu
  2. v2.332.0: Container jobs fail with permission denied on GITHUB_ENV and workspace after upgrade from

Photo by Rubaitul Azad on Unsplash