Storage I/O Throttling in Docker
How Docker uses the Linux kernel's cgroups to cap how fast a container can read from and write to disk — and why one noisy container without a limit can quietly starve every other workload on the same host.
A single container running an aggressive batch job — reindexing a database, decompressing a large archive, or writing verbose logs in a tight loop — can, without anyone changing a single line of application code, make every other container on the same Docker host feel sluggish. The disk itself has not gotten any bigger or smaller; it is simply being asked to do more work than it can handle at once, and by default Docker lets any container ask for as much of that work as it wants. Storage I/O throttling is the feature that fixes this: a way of telling Docker, and through Docker the Linux kernel, “this particular container may read or write disk data no faster than X,” so that no single workload can monopolize a shared, physical resource that every other container on the machine also depends on. This guide covers what storage I/O throttling actually is, how the kernel enforces it, and how to use it well in real multi-tenant systems.
Chapter One
ACore Concepts
Before looking at flags and numbers, it helps to understand what “I/O” means at the kernel level, and why disks in particular need this kind of protection in a way that CPU and memory sometimes do not.
What “I/O” means here
Input/output, or I/O, refers to any operation that moves data between a program and a storage device — reading a file, writing a log line, appending to a database’s data file, or flushing a cache to disk. Every one of these operations ultimately becomes a request sent down to the block device (the physical or virtual disk) beneath the filesystem. Storage I/O throttling operates at exactly that layer: the block device request layer, not the filesystem layer, which is why it can enforce limits consistently no matter which filesystem, database engine, or application is generating the requests.
Picture a shared office printer with one paper tray. Without any rule, whichever employee sends the biggest print job first can occupy the printer for the next twenty minutes, leaving everyone else’s single-page memo stuck in the queue. Storage I/O throttling is like installing a rule that says “no single employee’s job may print faster than 10 pages per minute” — big jobs still get done eventually, but no one job can hog the printer and starve everyone else waiting behind it.
Why disks need this more than most resources
CPU time can be sliced into extremely small intervals and shared fairly with almost no downside; memory can be reserved per-container with hard boundaries that are simple to enforce. A spinning or solid-state disk, by contrast, is a single physical device with a genuinely finite number of operations it can perform per second, and that ceiling is shared by every container on the host regardless of how the containers are otherwise isolated from each other. Without an explicit limit, the kernel’s default behavior is simply “first come, first served, as fast as the hardware allows,” which means one container’s disk-heavy workload can silently degrade every other container’s disk performance even though those other containers never asked for more resources than usual.
Docker’s storage I/O throttling actually offers two related but distinct kinds of caps: throughput limits, measured in bytes per second, and operation-rate limits, measured in I/O operations per second (IOPS). A workload doing a few enormous sequential reads and a workload doing thousands of tiny random reads can consume the same throughput while placing very different loads on the device, which is exactly why both units exist as separate controls.
Where this sits among Docker’s resource controls
Storage I/O throttling is one member of a small family of resource controls Docker exposes alongside CPU quotas and memory limits, all of which are really just a friendly interface over the same underlying Linux kernel feature: control groups, or cgroups. Where a CPU quota caps how much processing time a container’s processes may consume, and a memory limit caps how many bytes of RAM a container may hold, an I/O throttle caps how much traffic a container’s processes may send to a specific block device — the same governing philosophy applied to a different, equally finite, shared resource.
Chapter Two
BArchitecture & Components
Several layers cooperate to turn a limit you set on a container into an actual, enforced ceiling on disk traffic.
flowchart TB
APP["Container Process
(read/write syscall)"]
VFS["Virtual Filesystem Layer"]
BLK["Block Layer / I/O Scheduler"]
CG["cgroup blkio / io Controller
(per-device limits)"]
DEV["Physical Block Device"]
APP --> VFS --> BLK
BLK --> CG
CG -->|allowed| DEV
CG -.->|queued / delayed if over limit| BLK
cgroup blkio / io Controller
The Linux kernel component that tracks and enforces per-device I/O limits for a group of processes, available as the older “blkio” controller (cgroup v1) or the newer unified “io” controller (cgroup v2).
cgroup
Every container’s processes are automatically placed into their own cgroup when the container starts, giving Docker a ready-made handle to attach resource limits — I/O, CPU, and memory alike — to exactly that set of processes.
Runtime Flag Translator
Docker translates the limits you pass at container start time into the specific cgroup filesystem entries the kernel expects, then writes those values before the container’s first process begins running.
Major:Minor Device Number
Because a host may have several physical or virtual disks, every I/O limit is scoped to one specific block device, identified by its kernel-assigned major and minor number, not applied globally across all storage at once.
overlay2 / other graph drivers
The layer that implements a container’s writable filesystem on top of its read-only image layers; I/O generated by container writes still passes down through the block layer and is throttled the same way as any other disk traffic from that container.
Bind Mounts and Volumes
I/O against a bind-mounted host directory or a named volume is generated by the same container process and cgroup, so the same throttle applies to it exactly as it applies to the container’s own writable layer.
cgroup v1 versus cgroup v2
Two generations of this kernel subsystem exist, and Docker supports both, though the exact controls differ slightly. Under cgroup v1, the relevant controller is called blkio, and it exposes throughput and IOPS limits as separate read and write values per device. Under the newer, unified cgroup v2 hierarchy — now the default on most modern Linux distributions — the equivalent controller is simply called io, and it consolidates resource control into a single hierarchy shared with CPU and memory limits, which also enables more advanced proportional-weighting behavior alongside hard throttling. Docker abstracts most of this difference away behind the same command-line flags, detecting at runtime which cgroup version the host kernel is using.
How does the kernel actually slow a container down without simply rejecting its I/O requests outright?
From container start to enforced limit
When a container is started with an I/O limit configured, Docker first creates the container’s cgroup as it normally would, then writes the requested throughput or IOPS value into the appropriate cgroup control file for the target block device, scoped by that device’s major and minor number. From that moment forward, every read or write request the container’s processes issue against that specific device is intercepted by the kernel’s block layer before it reaches the device driver, checked against the cgroup’s configured limit, and either allowed through immediately or held back.
Throttling by delay, not by rejection
A critical detail: the kernel does not throttle a container by failing its read or write calls with an error. Instead, it throttles by delaying the request — the container’s process simply blocks, waiting, until enough time has passed that granting the request would keep the container’s average rate under its configured ceiling. From the application’s point of view, a throttled disk operation looks exactly like a slow disk operation, not a failed one; the system call eventually returns successfully, just later than it would have without the limit. This is deliberate: throttling is meant to simulate a slower device, not an unreliable one, so well-behaved applications experience degraded performance under pressure rather than errors they were never designed to handle.
sequenceDiagram
participant P as Container Process
participant BL as Block Layer
participant CG as cgroup io Controller
participant D as Disk
P->>BL: write() request
BL->>CG: check current rate vs limit
alt Under limit
CG->>D: forward request immediately
D-->>P: write() returns
else Over limit
CG->>CG: queue and delay request
Note over CG: wait until rate drops
under configured ceiling
CG->>D: forward request once allowed
D-->>P: write() returns (later)
end
Throughput limits versus IOPS limits under the hood
A bytes-per-second limit is enforced by tracking a rolling count of bytes transferred and delaying any request that would push that rolling total above the configured ceiling within the measurement window. An IOPS limit works the same way but counts discrete operations rather than bytes, which matters because a device’s true bottleneck is sometimes the number of separate operations it can service per second (especially for spinning disks doing random access) rather than the raw volume of data. A container capped only on throughput could still overwhelm a device by issuing an enormous number of tiny operations that individually move very little data but collectively saturate the device’s operation-handling capacity — which is exactly why Docker lets you configure both kinds of limits independently, and why production systems protecting a shared spinning-disk array often care more about the IOPS ceiling than the throughput ceiling.
Per-device, per-direction granularity
Limits are not a single number covering all disk activity; they are configured per block device and per direction — read throughput, write throughput, read IOPS, and write IOPS can each be set independently, and a container can have different limits on different devices if it has access to more than one. This granularity matters in practice: a container might reasonably need generous read throughput to serve cached data quickly while still needing a tight write throughput cap to prevent a logging bug from filling the host’s disk write bandwidth.
Chapter Four
DData Flow & Lifecycle
Following a single write operation from application code to physical disk, and tracing how limits are configured and can change across a container’s lifetime.
The lifecycle of a throttled request
Container Starts With Limits
Docker creates the container’s cgroup and writes the configured device I/O limits into the kernel’s control files before the container’s process begins running.
Application Issues I/O
The application inside the container calls an ordinary read or write function, unaware that any limit exists — no code changes or special libraries are required.
Block Layer Interception
The kernel’s block layer identifies which cgroup issued the request and checks the current usage rate for that container against its configured limit for the target device.
Allow or Delay
Requests under the limit pass straight through to the device; requests that would exceed it are queued and released only once the rolling rate has room again.
Completion Returned
Once the device services the request, its result is handed back up through the block layer to the application exactly as an unthrottled request would be, just with added latency if it was delayed.
Container Stops
The cgroup and its associated I/O limit configuration are torn down along with the container, freeing the full device bandwidth back to the remaining containers on the host.
Changing limits on a running container
Because the limit lives in the cgroup’s control files rather than being baked into the container image or its startup configuration permanently, an operator can update a running container’s I/O limits without restarting it, and the new ceiling takes effect on the very next I/O request the kernel evaluates. This makes I/O throttling a genuinely dynamic tool: a limit can be tightened temporarily during a known high-traffic period across the rest of the host, then relaxed again afterward, all without any container downtime.
Interaction with the host’s own I/O
It is worth noting that cgroup-based I/O throttling only governs processes running inside a cgroup — meaning it constrains containers relative to each other and relative to any other cgroup-scoped workload, but does not, by itself, throttle processes running directly on the host outside any container. A host process performing heavy disk activity outside the container cgroup hierarchy can still compete for the same physical device bandwidth that containers are being carefully rationed from, which is why comprehensive I/O governance on a busy host usually involves placing host-level services into their own cgroups as well, not just the containers.
Chapter Five
EAdvantages, Disadvantages & Trade-offs
Throttling trades away a container’s maximum possible speed in exchange for predictability for everyone sharing the same disk — understanding that exchange is the whole point of using the feature well.
Advantages
- Prevents one “noisy neighbor” container from starving disk access for every other workload on the host
- Makes disk-related latency for other services predictable and testable under load
- Lets operators safely co-locate a bursty batch job with latency-sensitive services on the same host
- Changes take effect on running containers without requiring a restart
- Degrades gracefully — throttled I/O is slow, not broken, so applications keep functioning
Disadvantages
- A limit set too low can bottleneck a legitimate, healthy workload and cause misleading latency alerts
- Requires knowing the underlying device’s real capacity — a limit set above hardware capability protects nothing
- Adds a layer of indirection that makes disk performance harder to reason about during debugging
- cgroup v1 and v2 differ enough that limits configured on one host may not translate identically to another
- Does not, by itself, guarantee fairness among containers that have no explicit limit set at all
Hard limits versus proportional weighting
Storage I/O throttling as described in this guide is a hard ceiling: a ceiling a container simply cannot cross regardless of how idle the rest of the host’s disk capacity currently is, even if no other container is using the disk at that exact moment. This is different from proportional weighting, a related but distinct cgroup feature that instead says “when contention exists, give this container twice the share of available bandwidth as that one,” without capping either container’s absolute maximum when the disk is otherwise idle. The trade-off is simple: a hard limit gives the strongest guarantee against worst-case interference but can waste idle capacity, while proportional weighting uses capacity more efficiently but offers a weaker guarantee under simultaneous heavy contention. Production systems facing predictable, adversarial noisy-neighbor risk generally prefer hard limits; systems optimizing for overall throughput under normal, cooperative conditions often prefer weighting instead.
Set limits based on measured, real device capacity — benchmark the actual disk or cloud volume rather than trusting a manufacturer’s marketing figure — and leave meaningful headroom below that measured ceiling when dividing capacity across containers, since real workloads rarely run in perfect isolation from host-level I/O like log rotation or the storage driver’s own background housekeeping.
Chapter Six
FSecurity
Storage I/O throttling is as much a security and availability control as it is a performance-tuning knob, particularly on hosts running workloads from different trust levels.
Throttling as a denial-of-service defense
Any multi-tenant Docker host that runs containers from different teams, customers, or trust levels without I/O limits is implicitly exposed to a disk-based denial-of-service condition: a single misbehaving or maliciously crafted container can issue an unbounded volume of disk writes, degrading or effectively halting disk-dependent operations for every other container sharing that device, without needing any special privilege beyond ordinary filesystem access. Because this can happen entirely by accident — a runaway logging loop, a misconfigured cache eviction policy, a bug that repeatedly rewrites the same large file — treating I/O limits purely as a performance nicety rather than a security boundary understates the risk they mitigate.
Unthrottled shared disk access is like a shared apartment building’s single water main with no per-unit flow restrictor. One tenant leaving a tap running at full pressure can drop water pressure for every other unit in the building, even though nothing was technically stolen or broken — the resource was simply consumed faster than the shared infrastructure could keep up with everyone’s needs at once.
Limits do not protect data, only capacity
It is worth being precise about scope: I/O throttling constrains how fast a container can move data, not what data it is allowed to read or write, and not whether it can read another container’s files. A container with a very tight I/O limit is just as capable of reading sensitive files it has legitimate filesystem access to as one with no limit at all — it will simply do so more slowly. Teams sometimes conflate resource throttling with access control; they solve different problems, and a well-secured multi-tenant host needs both filesystem-level access restrictions (volumes, mount permissions, user namespaces) and I/O throttling working together, not either one alone.
Context
A shared build-and-test host regularly ran ad-hoc containers submitted by many different engineering teams, several of which had, in the past, unintentionally saturated the shared disk with aggressive test-data generation scripts.
Decision
Every container launched through the shared job runner was given a conservative default write-throughput and write-IOPS ceiling unless a team explicitly justified and requested a higher limit for a specific job.
Consequence
Occasional individual jobs run measurably slower than their theoretical best case, but the host’s overall disk-related incident rate dropped sharply, since no single submitted job could again degrade every other team’s concurrently running jobs.
Chapter Seven
GMonitoring, Logging & Metrics
Observing whether a throttle is actively shaping behavior — and distinguishing a throttled container from a genuinely slow one — requires looking at the right layer of the stack.
Signals that indicate active throttling
The clearest direct signal that a container is being throttled is a rising count of “throttled” or “queued” events reported by the kernel’s cgroup I/O statistics for that container’s cgroup, alongside a measured throughput or IOPS value that sits persistently at, or just under, the configured limit rather than fluctuating naturally with workload demand. A container whose I/O usage graph shows a flat, hard ceiling exactly matching its configured limit, with request latency spiking whenever the application tries to push past that ceiling, is a textbook throttling signature rather than a symptom of a genuinely overloaded or failing disk.
| Observed Symptom | Likely Cause: Throttling | Likely Cause: Genuine Disk Overload |
|---|---|---|
| Throughput ceiling | Flat, exactly matches configured limit | Variable, tracks overall host contention |
| Other containers on host | Unaffected, performing normally | Also degraded simultaneously |
| cgroup throttle counters | Actively incrementing | Not present or not incrementing |
| Removing the limit | Immediately resolves the symptom | No change in behavior |
Building dashboards around throttling
Effective dashboards separate two related but distinct questions: “how much I/O is this container using” and “how often is this container being held back by its limit.” The first is answered by raw throughput and IOPS metrics per container; the second requires the cgroup’s own throttling counters, which many general-purpose container monitoring tools surface only if explicitly configured to collect cgroup I/O statistics rather than just aggregate disk usage. Teams that track only raw usage often miss the more actionable signal — a container consistently pinned at its ceiling with a growing throttle-event count is telling you its limit may need to be raised, well before application-level latency alarms would otherwise fire.
An alert defined as “page us if container write latency exceeds N milliseconds” can fire constantly and unhelpfully for a container that is simply operating correctly against an intentionally conservative I/O limit. Pair any latency-based alert with a check against the container’s configured throttle so on-call engineers can immediately distinguish “working as configured” from “something is actually broken.”
Chapter Eight
HDesign Patterns & Anti-patterns
Recurring, well-tested ways teams apply I/O throttling in production, and the recurring mistakes that undermine it.
Tiered Limits By Workload Class
Latency-sensitive services get a generous, rarely-hit ceiling; background batch and cleanup jobs get a deliberately conservative one, so the host’s disk capacity is allocated in line with actual business priority rather than arrival order.
Burst-Then-Throttle Onboarding
New batch jobs are first run with no limit in a controlled test environment to measure their natural I/O profile, and a production limit is then set just above that measured baseline, avoiding both over-restriction and under-protection.
Per-Tenant Ceiling on Shared Hosts
Every tenant’s containers on a shared, multi-tenant host receive an identical default I/O ceiling as a baseline fairness guarantee, independent of what any individual tenant’s workload happens to be doing.
Copy-Pasted Limits Across Different Disks
Applying the same numeric throughput limit to containers running on hardware with very different real disk capacities, producing limits that are meaninglessly loose on fast storage and crippling on slow storage.
Setting a Limit and Never Revisiting It
Leaving an I/O ceiling chosen years earlier in place after the workload, the disk hardware, or the number of co-located containers has changed substantially, quietly turning a once-reasonable limit into an active bottleneck.
Design pattern in practice: database plus backup sidecar
A common production pattern gives the primary database container a generous I/O ceiling to preserve query latency, while a companion backup or archival sidecar container performing large sequential writes runs under a much tighter ceiling — ensuring a nightly backup job can never compete meaningfully with live query traffic for the same underlying disk, even if both happen to run at the same time.
Chapter Nine
IBest Practices & Common Mistakes
Concrete guardrails for setting and maintaining I/O limits that actually protect a host rather than just existing on paper.
Benchmark before choosing a number
Measure the real, sustained throughput and IOPS capacity of the actual disk or cloud volume in use before setting any limit, rather than guessing or reusing a figure from unrelated hardware.
Set both throughput and IOPS limits together
A throughput-only limit leaves a container free to overwhelm a device with a flood of tiny operations, so pair it with an IOPS ceiling whenever the workload’s access pattern is not purely large sequential transfers.
Re-baseline limits after hardware or topology changes
Revisit configured ceilings whenever the underlying storage is upgraded, downgraded, or when the number of co-located containers on a host changes meaningfully.
Treating throttling as a substitute for capacity planning
A host that is chronically over-subscribed on disk capacity will simply have every container running at a throttled ceiling permanently; limits manage contention, they do not create capacity that does not exist.
Ignoring host-level I/O outside containers
Forgetting that log rotation, backup agents, or monitoring tools running directly on the host also consume the same physical disk bandwidth that container limits are trying to protect.
Setting identical limits for read and write without justification
Read and write workloads often have genuinely different real-world requirements; applying one blanket number to both directions can under-protect one and over-restrict the other.
Chapter Ten
JReal-World & Industry Examples
Storage I/O throttling shows up wherever a single physical disk is shared by workloads that must not be allowed to interfere with one another.
Multi-Tenant Container Hosting
Large-scale container hosting platforms apply per-tenant I/O ceilings as a core part of their fairness guarantees, ensuring one customer’s workload cannot degrade disk performance for every other customer’s containers on the same physical node.
Shared Build Runners
Continuous-integration platforms running many concurrent build containers on shared build machines commonly throttle each job’s disk I/O to keep build times predictable regardless of how many other builds happen to be running at the same moment.
Mixed OLTP and Batch Workloads
Organizations running latency-sensitive transactional databases alongside heavy analytical or batch-processing jobs on shared infrastructure use I/O throttling to keep the transactional workload’s latency stable during analytical processing windows.
Kubernetes Ephemeral Storage Limits
Kubernetes builds its own storage resource controls for pods directly on top of the same underlying cgroup I/O mechanism this article describes, exposing it through higher-level pod resource specifications.
Chapter Eleven
KFrequently Asked Questions
Chapter Twelve
LSummary and Key Takeaways
Storage I/O throttling turns a shared, finite physical resource into something every container on a host can rely on predictably.
Key Takeaways
- Storage I/O throttling caps how fast a container’s processes may read or write to a specific block device, enforced by the Linux kernel’s cgroup I/O controller rather than by Docker itself.
- Limits can be set independently for throughput (bytes per second) and operation rate (IOPS), and independently for reads and writes, giving fine-grained control over different workload profiles.
- The kernel enforces limits by delaying over-limit requests rather than rejecting them, so throttled applications experience added latency, not errors.
- Without explicit limits, a single disk-heavy container can silently degrade performance for every other container sharing the same physical device — the classic “noisy neighbor” problem.
- Effective limits require measuring real device capacity first; a limit set without benchmarking either protects nothing or needlessly bottlenecks a healthy workload.
- This mechanism underlies fairness guarantees across multi-tenant cloud hosting, shared CI/CD build runners, and orchestration platforms like Kubernetes, all built on the same underlying cgroup I/O primitive.