cgroup v2 Migration in Docker

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.

Everyday Analogy

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.

i
Not a Docker Feature

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.

1
UNIFIED HIERARCHY IN CGROUP V2
3+
SEPARATE HIERARCHIES TYPICAL IN CGROUP V1
Kernel
LEVEL FEATURE, NOT A DOCKER SETTING

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
Fig. 1 — cgroup v1’s separate controller trees collapse into one shared tree under cgroup v2
v1 Structure

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.

v2 Structure

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.

Boot Configuration

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.

Docker Component

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.

Detection Layer

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.

Compatibility Layer

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.

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
Fig. 2 — Docker detects the host’s cgroup version once at daemon startup and translates limits accordingly for every container it launches

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.

1

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.

2

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.

3

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.

4

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.

5

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.

6

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.

“A cgroup v2 migration is rarely about containers failing to start — it’s about the handful of tools quietly watching the old hierarchy’s files that nobody remembered were looking there at all.”

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.

i
Low-Risk Path

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.

Security Analogy

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.

ADR-045: Staged Fleet-Wide cgroup v2 RolloutAdopted
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.

CheckWhat Success Looks LikeWhat a Failed Migration Looks Like
Active cgroup versionHost reports cgroup v2 as mountedHost still reports v1, or a hybrid state
Docker daemon startup logsConfirms cgroup v2 detected and in useErrors or fallback warnings during startup
Resource limit enforcementTest container’s configured limits are honoredLimits silently not applied or misapplied
Direct-reading toolingReports the same data as before migrationReports 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.

!
Monitoring Gap Risk

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.

Pattern

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.

Pattern

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.

Pattern

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.

Anti-pattern

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.

Anti-pattern

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.

Concrete, practical steps that separate a smooth cgroup v2 migration from a confusing multi-day incident.

Do

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.

Do

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.

Do

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.

Avoid

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.

Avoid

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.

Avoid

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.

Checklist Before scheduling a cgroup v2 migration, confirm: (1) Docker, the container runtime, and any orchestration layer are on versions with full cgroup v2 support, (2) every tool reading cgroup data directly has been inventoried and updated, (3) the migration is scheduled as a planned reboot during a low-traffic maintenance window, and (4) resource-limit enforcement and monitoring dashboards will be explicitly validated immediately after the switch.

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.

Distributions

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.

Orchestration

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.

Cloud Providers

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.

Tooling Ecosystem

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.

Default
ON MOST CURRENT LINUX DISTRIBUTIONS
1
REBOOT REQUIRED PER HOST TO SWITCH VERSIONS
Fleet-Wide
SCOPE FOR A FULL MIGRATION PROJECT

Chapter Eleven

KFrequently Asked Questions

Q1Do I need to change my Docker commands or Compose files to use cgroup v2?
No. Docker’s command-line flags and Compose configuration options remain the same across both versions; Docker’s daemon internally translates them to the version-appropriate format.
Q2Can I migrate a single running container to cgroup v2 without touching the whole host?
No. The cgroup version is a host-wide, boot-time setting, so it applies to every container on that host simultaneously and requires a host reboot to change, not a per-container action.
Q3Will my existing resource limits (CPU, memory, I/O) still work the same way after migrating?
Yes, for the vast majority of workloads the enforced behavior is designed to be equivalent, though tools reading the underlying cgroup files directly rather than through Docker may need updates to keep working correctly.
Q4How can I tell which cgroup version my current host is actually using?
The host’s mounted filesystem layout for the cgroup subsystem, along with the Docker daemon’s own startup information, indicates which version is active; this is typically the very first thing checked before planning any migration.
Q5Is it possible to run cgroup v1 and v2 at the same time during a transition?
A hybrid configuration is possible on some systems as a transitional bridge, but it adds operational complexity and is generally treated as a temporary state during a gradual rollout rather than a stable, permanent target.
Q6Does migrating to cgroup v2 improve container performance?
The primary benefits are consistency, accuracy, and safer delegation rather than raw speed; most ordinary workloads will not notice a meaningful performance difference purely from the version change itself.

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.