Docker cannot enter cgroupv2, and cgroup mount errors in CI
Docker cannot enter cgroupv2, and the cgroup mount errors beside it, mean the runtime could not place your container into a control group, which on a CI runner nearly always means a daemon running nested inside another container without the v2 controllers delegated to it. Your image and your command are not involved: the container has nowhere to be accounted.

What this error means
A container refuses to start and the message points at /sys/fs/cgroup rather than at anything you wrote, often with the phrase "with domain controllers" and a note that the state is invalid. It appears in Docker-in-Docker jobs, in Kubernetes-in-Docker tools, and in build engines that run their own daemon in a container, and it does not appear on a plain hosted runner doing a plain docker run. We did not reproduce it: it needs a nested daemon in a particular broken delegation state, and building one to photograph it would teach you less than the delegation rules below. The line below is quoted in full from containerd/containerd#6659, ctr prefix and line numbers included, because the clause that names the cause is in the middle of it: applying cgroup configuration for process. The Go file and line numbers move between runc releases, so match on that clause rather than on them.
ctr: failed to create shim task: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:385: applying cgroup configuration for process caused: cannot enter cgroupv2 "/sys/fs/cgroup/default" with domain controllers -- it is in an invalid state: unknownWhat changes between cgroup v1 and v2
Docker documents the hierarchy as living under /sys/fs/cgroup on modern distributions, in the page we read on 2026-09-20, and the defaults differ by version: the cgroup driver, set with dockerd --exec-opt native.cgroupdriver, is systemd on v2 and cgroupfs on v1, and the container cgroup namespace mode is private on v2 and host on v1. Ubuntu has defaulted to v2 since 21.10, so every current hosted Linux runner image is a v2 host.
On a v2 host the hierarchy is unified and a process can only manage the part of the tree it was delegated. That is the whole story of this error: a daemon inside a container is handed a subtree, and if the controllers it needs were not enabled there, it cannot create the child cgroup a new container requires.
| Setting | cgroup v1 | cgroup v2 |
|---|---|---|
| Default cgroup driver | cgroupfs | systemd |
Default --cgroupns | host | private |
| Where the hierarchy is | Per-controller trees under /sys/fs/cgroup | One unified tree under /sys/fs/cgroup |
| How to tell | No cgroup.controllers file | /sys/fs/cgroup/cgroup.controllers exists |
Common causes
Controllers were never delegated to the nested environment
The dominant cause. A daemon inside a container gets a cgroup subtree, and the controllers it needs have to be enabled in that subtree by whatever created it. When they are not, the runtime cannot create the child cgroup for a new container, and reports that it cannot enter the cgroup it was given.
The cgroup mount is missing or read-only inside the container
A nested daemon needs /sys/fs/cgroup present and writable for its own subtree. A hardened container, a read-only mount, or a sandbox that hides the hierarchy produces mount errors in the same family, with the same effect: nothing can be placed in a cgroup.
The cgroup driver does not match the host
On a v2 host the default driver is systemd. A nested runtime or a tool configured for cgroupfs will fight the systemd hierarchy, and in our experience this is the cause when the error changes shape between runs rather than being stable.
The runtime predates proper cgroup v2 support
Older runc and containerd releases handle the unified hierarchy badly, which is what containerd/containerd#6659 is about. A pinned old runtime in a custom image, or a base image nobody has refreshed, is enough to reproduce a bug that current versions do not have.
How to fix it
Use the daemon the runner already has
- Drop the
docker:dindservice and run Docker commands directly in the job. - If a tool insists on its own engine, check whether it has a mode that reuses the host daemon.
- Keep nesting for cases that genuinely need isolation between builds.
- run: docker info --format '{{.CgroupVersion}} {{.CgroupDriver}}'
- run: docker build -t app .Give the nested daemon the cgroup environment it needs
When you do nest, run the inner daemon with the host cgroup namespace and the privileges to manage its subtree, and mount the hierarchy so it is writable. This is a deliberate reduction in isolation, so scope it to the job that needs it rather than making it the default.
docker run --privileged --cgroupns=host \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
docker:29-dindAlign the cgroup driver end to end
Pick one driver and configure every layer for it. On a v2 host that is systemd unless you have a reason. Docker accepts only cgroupfs or systemd and refuses to start when systemd is requested and unavailable, so a mismatch fails loudly rather than silently.
{ "exec-opts": ["native.cgroupdriver=systemd"] }Update runc and containerd before debugging further
If the stack is pinned to a release from the early cgroup v2 era, upgrade first and retest. A large share of reports against this error resolve into a runtime that was fixed years ago, which is cheaper to rule out than to reason about.
runc --version
containerd --version
docker info --format '{{.ServerVersion}}'This error is a nesting error
The wording that names domain controllers and an invalid state comes from a runtime trying to move a process into a cgroup that is a domain with controllers enabled, which the v2 rules do not allow. containerd/containerd#6659 is the canonical report: containerd 1.6.x running inside a Docker container, unable to run containers. Dagger and Earthly hit the same wall from their own engines, in dagger/dagger#6364 and earthly/earthly#3205.
It is also rare, which is itself diagnostic. A GitHub issue search for the phrase together with "github actions" returned 11 results when we ran it on 2026-09-20, against 467 for "OCI runtime exec failed" the same day. If you are seeing it, you are almost certainly running a daemon inside a container rather than using the runner daemon.
# which version, and what is delegated here
cat /sys/fs/cgroup/cgroup.controllers 2>&1 || echo "cgroup v1 host"
docker info --format '{{.CgroupDriver}} {{.CgroupVersion}}'The cheapest fix is to stop nesting
A hosted Linux runner already has a working daemon. A job that runs docker:dind as a service container to get one is paying for a second daemon, a second image cache and this class of failure, usually because the workflow was ported from a platform where the runner had no Docker at all.
When nesting is genuinely required, give the nested daemon the environment it needs rather than hoping: the host cgroup namespace, a writable cgroup mount, and enough privilege to create the subtree. Then align the drivers, because a nested runtime expecting cgroupfs on a systemd host produces the neighboring mount errors.
# prefer this on a runner that already has Docker
- run: docker build -t app .
# instead of a dind service plus DOCKER_HOST gymnasticsWhat the runner does about it
No repair, and no reproduction to quote. A Latchkey latchkey-small runner gives a job a daemon on the host, which is the configuration where this failure does not arise, so there is no honest run for us to record here. Nothing in the self-heal library targets cgroup delegation, and it should not: changing a delegation boundary underneath a running job is not a safe automatic action.
The practical advice is the same as the section above. Use the daemon the runner gives you; if you need a nested one, set its cgroup namespace and privileges explicitly, and print docker info once so the log records which driver and version were in play when it failed.
How to prevent it
- Use the runner daemon rather than a nested one wherever the job allows it.
- If you nest, pin the cgroup namespace and mounts explicitly in the workflow.
- Keep runc and containerd current in any custom runner image.
- Print the cgroup version and driver once per job that runs containers.
Frequently asked questions
Which cgroup version do GitHub Actions runners use?
How do I tell which cgroup version a runner is on?
/sys/fs/cgroup/cgroup.controllers: it exists on a v2 host and does not on v1. docker info reports the version and the driver together, which is the line worth printing in any job that runs containers, because it makes the next failure readable.Do I need --cgroupns=host for Docker-in-Docker?
Is this error common in CI?
Related guides
References
- Docker docs: runtime metrics, cgroup v1 and v2 defaults
- Docker docs: dockerd, native.cgroupdriver
- containerd/containerd#6659: cannot enter cgroupv2 with domain controllers
- dagger/dagger#6364: the same failure from an engine running in a container
- Docker documentation
- Docker build cache
- GitHub Actions documentation