cgroup v2 Migration in Docker
How Docker moved from the fragmented, decade-old cgroup v1 hierarchy to the unified cgroup v2 model — and what actually changes underneath your containers when a host makes that switch.
Every resource limit a Docker container ever obeys — how much CPU it may use, how much memory it may hold, how fast it may read or write disk — is ultimately enforced by a single Linux kernel subsystem called control groups, or cgroups. For most of Docker’s history, that subsystem existed in a first-generation form now known as cgroup v1: a design that worked, but that split CPU, memory, and I/O accounting into separate, loosely related hierarchies that did not always agree with each other. cgroup v2 is the kernel’s from-scratch rewrite of that same idea, unifying every resource controller under one single, consistent hierarchy. cgroup v2 migration is the process — sometimes automatic, sometimes requiring deliberate host configuration — of moving a Docker host from the old v1 model to the new v2 model, and it is one of those rare infrastructure changes that is almost entirely invisible when it goes well, and highly confusing when it doesn’t. This guide explains what actually differs between the two versions, how the migration happens in practice, and what to check before and after making the switch.
Chapter One
ACore Concepts
Before comparing versions, it helps to understand what a cgroup actually is and why the original design eventually needed replacing.
What a cgroup does, in one sentence
A control group is a Linux kernel mechanism that groups a set of processes together so that resource limits — CPU time, memory, disk I/O, and more — can be applied to that group as a whole rather than to each process individually. Every Docker container’s processes are automatically placed into their own cgroup the moment the container starts, which is the single mechanism underneath essentially every resource control Docker exposes, including CPU quotas, memory limits, and the storage I/O throttling covered elsewhere in this series.
Imagine an office building where electricity, water, and internet bandwidth each had a completely separate metering and permission system, run by three different departments that didn’t talk to each other — the electricity department’s floor plan didn’t even match the water department’s floor plan. cgroup v1 worked roughly like that: separate hierarchies for CPU, memory, and I/O that could, in awkward edge cases, actually group processes differently from each other. cgroup v2 is like consolidating all three utilities under one single building management system with one shared floor plan, so “which room is this desk in” means the same thing no matter which utility you’re asking about.
Why cgroup v1’s design became a problem
In cgroup v1, each resource controller — CPU, memory, block I/O, and others — maintained its own independent hierarchy of groups, and nothing in the kernel required those hierarchies to mirror each other. A system could, in principle, end up with a process grouped one way for CPU accounting and a different way for memory accounting, which made certain coordinated resource decisions (like accurately measuring total resource pressure a group of processes was experiencing) genuinely difficult to implement correctly. Over time this fragmentation, along with several other rough edges around how limits interacted with delegation and unprivileged use, motivated the kernel community to design cgroup v2 as a deliberate, unified replacement rather than an incremental patch.
cgroup v2, like cgroup v1 before it, is a Linux kernel and distribution-level feature, not something Docker itself invented or controls. Docker’s role in this migration is entirely about detecting which version the host kernel is using and adapting its own internal behavior to configure limits correctly against whichever version is present.
Where things stand today
Most mainstream Linux distributions released in recent years default to cgroup v2 out of the box, and modern versions of Docker fully support it as the expected, preferred configuration. This means that for many engineers today, “migration” is not something they actively perform themselves at all — it already happened, silently, the moment their organization adopted a sufficiently recent Linux distribution or cloud base image. The migration remains directly relevant, however, to any team still running older hosts, custom kernel configurations, or environments where cgroup v1 was deliberately retained for compatibility with older tooling.
Chapter Two
BArchitecture & Components
Understanding the structural difference between the two cgroup generations makes clear exactly what a migration actually changes on a host.
flowchart LR
subgraph V1["cgroup v1 — Separate Hierarchies"]
CPU1["CPU Hierarchy"]
MEM1["Memory Hierarchy"]
IO1["blkio Hierarchy"]
end
subgraph V2["cgroup v2 — Unified Hierarchy"]
ROOT["Single Hierarchy"]
CTRL["cpu, memory, io controllers
attached to same tree"]
ROOT --- CTRL
end
V1 -->|Migration| V2
Multiple Hierarchies
Each resource controller (CPU, memory, blkio, and others) is mounted as its own independent virtual filesystem tree, each of which can group processes differently from the others.
Unified Hierarchy
All resource controllers attach to a single shared tree of cgroups, so a process’s grouping for CPU purposes is guaranteed to match its grouping for memory and I/O purposes.
Kernel Command Line / systemd
Which cgroup version a host actually uses is typically determined by a kernel boot parameter or, on most modern distributions, by the init system’s own default configuration rather than anything Docker itself decides.
cgroup Driver
Docker’s own internal setting determining how it talks to the cgroup subsystem — historically offered as either a “cgroupfs” driver or a “systemd” driver, with systemd increasingly the recommended choice under cgroup v2.
Runtime Version Check
Both Docker’s daemon and the lower-level container runtime detect which cgroup version is mounted on the host at startup and adjust how they write resource-limit values accordingly.
Hybrid Mode
Some transitional host configurations run a hybrid setup with both cgroup versions partially mounted simultaneously, generally as a temporary bridge during migration rather than a stable long-term target.
What Docker itself needs to support the switch
Supporting cgroup v2 was not automatic for Docker — the daemon, the underlying container runtime, and the low-level tools they depend on all needed updates capable of writing resource-limit configuration in the format the new unified hierarchy expects, since the control files and their exact semantics differ meaningfully between the two versions. This is why cgroup v2 support arrived in Docker as a distinct, versioned milestone rather than being available from Docker’s earliest releases, and why running cgroup v2 successfully requires a sufficiently modern Docker version in addition to a host kernel and distribution that support it.
Chapter Three
CInternal Working
A closer look at what actually happens on a host during the transition, and how Docker’s behavior differs depending on which version it finds.
How the host decides which version to boot into
Which cgroup version is active on a given boot is determined very early in the startup process, generally by the distribution’s init system and a kernel boot parameter, well before Docker’s own daemon ever starts. Docker does not choose this on its own; it inspects the already-mounted cgroup filesystem at daemon startup and adapts. This ordering matters because it means a genuine version migration on an existing host is fundamentally a host-level and reboot-level change, not something achieved purely by upgrading or reconfiguring Docker itself.
How Docker’s daemon adapts its behavior
Once the Docker daemon starts, one of its early steps is checking which cgroup version the kernel has mounted. If it finds cgroup v1, it writes resource limits using the older, controller-specific file paths and semantics that generation expects. If it finds cgroup v2, it instead writes limits against the new unified hierarchy’s control files, which in several cases use genuinely different file names, different units, or different semantics for conceptually the same setting — for example, several limit values move from separate per-controller files into fewer, more structured files under the unified model. This detection-and-adaptation logic is precisely what allows the exact same Docker command-line flags and Compose configuration to keep working unchanged for the end user, even though the underlying kernel interaction looks quite different depending on the host’s cgroup version.
sequenceDiagram
participant B as Host Boot
participant K as Kernel / init
participant D as Docker Daemon
participant R as Runtime (runc)
participant C as Container
B->>K: mount cgroup filesystem (v1 or v2)
D->>K: detect mounted cgroup version
Note over D: same docker run flags,
different internal translation
D->>R: resource limits in version-appropriate format
R->>C: cgroup created and configured
What genuinely changes for a running container
From inside a container, the practical experience of resource limits is designed to feel identical across both versions for the overwhelming majority of ordinary workloads — a memory limit still caps memory, a CPU quota still caps CPU time. The differences that matter in practice surface mostly at the edges: certain very low-level tools or monitoring agents that read cgroup control files directly, rather than through Docker’s own interface, may need updating if they hardcoded cgroup v1 file paths, since those exact paths and file formats do not carry over unchanged to cgroup v2.
Chapter Four
DData Flow & Lifecycle
What an actual migration project looks like in practice, from a v1 host to a fully v2 one, and what happens at each stage.
Assess Current State
Determine which cgroup version the host currently boots into, and inventory any tooling, monitoring agents, or scripts that interact with cgroup control files directly rather than through Docker.
Verify Docker and Runtime Versions
Confirm the Docker daemon and underlying container runtime installed on the host are new enough to fully support cgroup v2, upgrading first if necessary.
Update Direct cgroup-Reading Tooling
Update or replace any monitoring, security, or debugging tools identified in the assessment step that assume cgroup v1’s file layout, since these will otherwise silently stop reporting correct data after the switch.
Enable cgroup v2 at the Host Level
Reconfigure the host’s boot parameters or init system to mount cgroup v2 as the active hierarchy, a change that takes effect on the next reboot.
Reboot and Verify
After the reboot, confirm the host is genuinely running cgroup v2, that Docker’s daemon started successfully against it, and that resource limits on test containers are being enforced as expected.
Monitor Steady State
Watch resource-limit enforcement, monitoring dashboards, and any previously-flagged tooling closely for a period after the switch, since some edge-case dependencies only surface under specific, infrequent conditions.
Container restart behavior during migration
Because the cgroup version is fixed for the entire duration of a boot, containers already running when a host is switched over cannot be transparently migrated in place from a v1 cgroup to a v2 cgroup — the transition genuinely requires the host, and therefore every container on it, to restart. This makes cgroup v2 migration fundamentally a maintenance-window activity for any host running production workloads, not a rolling, zero-downtime change applied to individual containers one at a time.
Chapter Five
EAdvantages, Disadvantages & Trade-offs
cgroup v2 is broadly a strict improvement over v1, but the migration itself carries real, practical costs worth weighing honestly.
Advantages
- Unified hierarchy guarantees consistent process grouping across CPU, memory, and I/O controllers
- Cleaner delegation model, making it safer to allow unprivileged or nested cgroup management
- Improved memory pressure and I/O accounting accuracy compared to the older, more fragmented v1 model
- Actively maintained as the kernel community’s primary focus, with new resource-management features landing in v2 rather than v1
- Already the default on most current Linux distributions, meaning new hosts typically need no migration at all
Disadvantages
- Requires a full host reboot, making it a planned maintenance activity rather than a live, rolling change
- Tooling that reads cgroup control files directly, rather than through Docker’s abstraction, may need updates
- Requires a sufficiently modern Docker and container runtime version, which older, unpatched hosts may lack
- Mixed fleets running both versions simultaneously during a gradual migration add operational complexity to monitoring and tooling
- Some legacy container images or init systems bundled inside images assume v1-specific behavior
Why the trade-off almost always favors migrating
Because the Linux kernel community’s ongoing development effort is concentrated on cgroup v2, and because most mainstream distributions have already made it the default, remaining on cgroup v1 increasingly means missing new resource-management capabilities and, over a longer horizon, running against a subsystem receiving less active attention than its successor. For most organizations the practical question is not whether to migrate but when, and the disadvantages listed above are almost entirely about the mechanics and timing of the migration itself rather than the destination state being genuinely worse.
For organizations building new infrastructure rather than migrating existing hosts, the lowest-risk path is simply choosing a current Linux distribution and Docker version from the start, which typically means landing on cgroup v2 by default with no explicit migration project required at all.
Chapter Six
FSecurity
cgroup v2’s design changes carry genuine, if often underappreciated, security implications beyond pure resource management.
Delegation and reduced privilege requirements
One of cgroup v2’s explicit design goals was making it safer to delegate control over a portion of the cgroup hierarchy to a less-privileged process, a capability that matters directly for how container runtimes and nested container scenarios (containers running other containers) can be built with tighter privilege boundaries than cgroup v1 comfortably allowed. This underpins some newer container runtime security models that would have been considerably harder, or outright unsafe, to build cleanly on top of cgroup v1’s looser delegation semantics.
cgroup v1’s separate hierarchies were a bit like a building where the electrical, plumbing, and security systems each had their own separate, inconsistent access-control lists — handing a contractor limited access to “just the plumbing” required carefully auditing three different systems to be sure nothing else leaked through. cgroup v2’s unified hierarchy is like a single access-control system covering all building systems at once, making it far easier to confidently hand over a narrowly scoped slice of control without accidentally granting more than intended.
Migration-specific security considerations
During an active migration, hosts running a hybrid or mixed configuration deserve extra scrutiny, since resource limits and any security tooling built on top of them may behave inconsistently if some processes are still governed under v1 semantics while others have moved to v2. Security and monitoring tooling that has not yet been updated to understand cgroup v2’s file layout can silently fail to observe resource usage correctly during this window, which is a meaningful risk if that same tooling is relied on to detect resource-based anomalies, such as a container attempting to consume dramatically more CPU or memory than expected.
Context
An infrastructure team needed to migrate several hundred production hosts still running cgroup v1 to cgroup v2, and discovered that one of their custom security monitoring agents read cgroup files directly using hardcoded v1-specific paths.
Decision
The monitoring agent was updated and validated against cgroup v2 first, in an isolated test environment, before any production host’s cgroup version was changed, and the fleet-wide migration proceeded in small, monitored batches rather than all at once.
Consequence
The migration took longer than a single coordinated cutover would have, but avoided any window in which the security monitoring agent’s coverage was degraded across a large portion of the production fleet simultaneously.
Chapter Seven
GMonitoring, Logging & Metrics
Confirming a migration actually succeeded, and keeping resource visibility intact throughout, requires checking specific, version-aware signals.
Confirming which version is actually active
The most fundamental post-migration check is straightforward but easy to skip under time pressure: explicitly confirming the host has genuinely booted into cgroup v2 rather than silently falling back to v1 due to a missed configuration step, and confirming the Docker daemon itself has detected and is operating against that version correctly. Skipping this confirmation step and simply assuming success based on containers appearing to start normally is a common source of migrations that are believed complete but are not.
| Check | What Success Looks Like | What a Failed Migration Looks Like |
|---|---|---|
| Active cgroup version | Host reports cgroup v2 as mounted | Host still reports v1, or a hybrid state |
| Docker daemon startup logs | Confirms cgroup v2 detected and in use | Errors or fallback warnings during startup |
| Resource limit enforcement | Test container’s configured limits are honored | Limits silently not applied or misapplied |
| Direct-reading tooling | Reports the same data as before migration | Reports missing, zero, or stale values |
Watching for silently broken observability
The most dangerous class of post-migration problem is not a container that fails to start, which is immediately obvious, but a monitoring or alerting pipeline that continues running without any visible error while quietly reporting incorrect or missing resource data, because it never adapted to the new cgroup v2 file layout. Because this failure mode produces no crash and no obvious alert of its own, it is best caught by proactively validating that key resource dashboards and alerts still populate correctly with real data immediately following the migration, rather than waiting to notice their absence only when a genuine incident occurs and the data turns out not to be there.
A dashboard showing flat, unchanging, or zero resource metrics immediately after a cgroup v2 migration is far more likely to indicate a monitoring pipeline that has not been updated for the new file layout than it is to indicate genuinely idle containers — treat it as a monitoring integrity issue first, not a workload change.
Chapter Eight
HDesign Patterns & Anti-patterns
Approaches that make a fleet-wide cgroup version migration manageable, and the recurring mistakes that turn it into an incident.
Canary Host Validation
Migrate a small number of representative, non-critical hosts first, run them under real workload for a meaningful period, and use the results to refine the process before touching the broader fleet.
Tooling Inventory Before Migration
Explicitly catalog every piece of software on a host that reads cgroup data directly rather than through Docker’s own interface, since these are precisely the components most likely to break silently.
Version-Aware Base Images
Building and testing container images against both cgroup versions where feasible, particularly for images that embed monitoring agents or init systems with their own cgroup assumptions.
Migrating During Peak Traffic
Scheduling a host-wide cgroup version switch, which requires a reboot and therefore container downtime on that host, during a period of high production load rather than a planned maintenance window.
Assuming Docker Compatibility Means Full Tooling Compatibility
Confirming only that Docker itself supports cgroup v2 while overlooking third-party monitoring, security, or debugging tools that separately, and independently, need their own updates.
Pattern in practice: mixed-version fleet during a gradual rollout
A common real-world approach for larger fleets is deliberately running both cgroup versions across different host groups for a defined transition period, with monitoring and alerting explicitly built to tag and interpret data from each group correctly, rather than assuming a single, fleet-wide cgroup version at any given moment during the rollout.
Chapter Nine
IBest Practices & Common Mistakes
Concrete, practical steps that separate a smooth cgroup v2 migration from a confusing multi-day incident.
Verify Docker and runtime versions first
Confirm every component in the container stack — daemon, runtime, and any orchestration layer involved — meets the minimum version required for full cgroup v2 support before scheduling the host-level switch.
Treat it as a reboot-requiring maintenance activity
Plan the migration with the same rigor as any other planned reboot of production infrastructure, including appropriate scheduling, communication, and rollback planning.
Validate resource limits after the switch, not just container startup
Confirm that CPU, memory, and I/O limits are actually being enforced correctly post-migration with a deliberate test, rather than assuming success from containers simply starting without error.
Skipping the tooling inventory step
Moving directly to migration without first identifying every tool that reads cgroup data directly is the single most common source of post-migration surprises.
Migrating the entire fleet in one step
A single, all-at-once, fleet-wide cutover removes the opportunity to catch and fix an unanticipated issue on a small scale before it affects every host at once.
Forgetting images with their own embedded assumptions
Some container images bundle their own init systems or agents with hardcoded cgroup v1 assumptions baked in, independent of the host’s Docker and kernel configuration.
Chapter Ten
JReal-World & Industry Examples
The industry-wide shift to cgroup v2 has touched nearly every major piece of container infrastructure, not just Docker in isolation.
Modern Linux Default Behavior
Most current mainstream Linux distributions now boot into cgroup v2 by default, meaning organizations adopting current-generation base operating system images typically inherit cgroup v2 without any explicit migration project of their own.
Kubernetes cgroup v2 Support
Kubernetes itself required corresponding updates across its own components to fully and correctly support cgroup v2 clusters, since it sits directly on top of the same underlying container runtime and cgroup mechanisms Docker uses.
Managed Container Platforms
Major managed container and Kubernetes hosting platforms have progressively updated their default host images to cgroup v2, effectively performing the migration on behalf of customers who use those managed offerings rather than self-managed hosts.
Monitoring and Security Vendors
Container observability and security tooling vendors across the industry updated their agents to understand cgroup v2’s file layout as adoption grew, a process that took real, sustained engineering effort industry-wide rather than happening automatically.
Chapter Eleven
KFrequently Asked Questions
Chapter Twelve
LSummary and Key Takeaways
cgroup v2 migration replaces a fragmented, decade-old resource-management foundation with a unified, more consistent one, at the cost of a genuine, reboot-requiring transition.
Key Takeaways
- cgroup v2 unifies the separate CPU, memory, and I/O hierarchies of cgroup v1 into a single, consistent hierarchy, guaranteeing that a process’s grouping is the same across every resource controller.
- Docker detects which cgroup version the host kernel is using at daemon startup and internally translates the same command-line flags and Compose configuration into the correct format for either version.
- The cgroup version is a host-wide, boot-time setting; migrating requires a full host reboot and cannot be applied to individual containers in isolation.
- The most common source of post-migration problems is not Docker itself but third-party monitoring, security, or debugging tooling that reads cgroup files directly using hardcoded v1-specific paths.
- Most current mainstream Linux distributions now default to cgroup v2, meaning many organizations inherit it automatically through new infrastructure rather than needing an explicit migration project.
- A successful migration is confirmed not just by containers starting normally, but by explicitly validating that resource limits are enforced correctly and that monitoring dashboards continue reporting accurate data afterward.