· via dev.to (home feed)
Docker Engine 29's new default image store silently ignores --storage-opt size quotas
Docker Engine 29 defaults new installs to the containerd snapshotter, and hands-on testing shows --storage-opt size is accepted but never enforced — while image pulls run about 37% faster.

Docker Engine 29 ships with a foundational default flip: fresh installations now use the containerd snapshotter as their image store instead of the classic overlay2 graphdriver. According to a hands-on comparison published on dev.to, that change brings a real operational regression along with it — the --storage-opt size option, used to cap a container's writable layer, is now accepted but never enforced, with no error, warning, or log entry.
A quota that stops working and stops complaining
The --storage-opt size flag has long had strict preconditions: it only functions on overlay2 running over XFS mounted with the pquota option. The test host was a fresh Docker 29.3.1 install with a plain ext4 root filesystem and no project quota mount option; the backend could be toggled with a daemon feature flag and confirmed via docker info.
Under the classic graphdriver, Docker handles this correctly and visibly. Running a container with --storage-opt size=100M fails every time with exit code 125 and an explicit daemon error stating the option is supported only for overlay over XFS with the pquota mount option. The container never starts, which is exactly what you want when a host cannot honour the request.
Under the containerd snapshotter — the Docker 29 default — the identical command on the identical filesystem exits 0, and a shell inside the container wrote a 300MB file against the requested 100MB cap. The result repeated across three runs. Compounding the problem, docker inspect continues to report the option as set (map[size:100M]), so any compliance script, audit tool, or operator reading container configuration will conclude the limit is active. The author's assessment of the cause: the containerd image store path does not validate backing filesystem support before accepting the option, whereas the graphdriver path does — an unimplemented check rather than a deliberate behaviour change.
Pulls are genuinely faster
The switch is not without upside. Pulling node:22 — 8 layers, 1.64GB unpacked — from a clean state averaged 18.0 seconds on the snapshotter versus 28.5 seconds on overlay2, roughly 37% faster and consistent across three runs per backend. The author notes the test ordering rules out warm-cache bias favouring the snapshotter, since the slower graphdriver runs happened later in the sequence.
An experiment around max-concurrent-downloads went against expectations. The Docker 29 release notes describe a bug, fixed in 29.7.0, in which concurrent pull limits were not honoured on the snapshotter path, and the author expected to reproduce it on 29.3.1. Instead, the setting barely moved the needle on either backend: 18.0s to 15.0s on the snapshotter and 28.5s to 27.5s on overlay2. The conclusion drawn from those numbers is that layer unpacking into the filesystem, not the number of concurrent HTTP downloads, dominates pull wall-clock time on this setup.
Image mounts and dedup hold up
Two further checks came out clean. --mount type=image, which the release notes say graduates out of experimental status in 29.7.0, still carries an experimental warning on 29.3.1 but behaved correctly in testing: the mount is read-only (a write attempt fails with a read-only filesystem error), and a bad source image or a missing target fails cleanly with exit code 125. A subpath variant was not recognised on this build. Disk accounting also survived the backend change: docker system df -v correctly attributed 87.45MB of shared Debian base layers between python:3.12-slim and python:3.13-slim, matching on both images.
Why it matters
A silent quota failure is close to the worst failure mode an ops team can inherit from an upgrade. Under overlay2, an unsatisfiable --storage-opt size produced a loud, blocking error; under the new default it produces a clean exit code, a docker inspect value that looks legitimate, and no actual cap on the disk a container can consume. The likely blast radius is a runaway container filling a host disk that several other containers also write to, discovered only when something else breaks.
Teams that rely on per-container disk quotas — especially for untrusted or multi-tenant workloads — should verify their backing filesystem supports project quotas before moving to Docker 29, and treat docker inspect output as evidence of intent rather than enforcement. The faster pulls are real and reproducible, but they arrive bundled with a default that trades a loud failure for a quiet one.
- #docker
- #containerd
- #containers
- #devops
- #storage