One container writes logs at an insane rate. A database import fills every available sector. A media scanner generates thumbnails until the disk shows zero bytes free. Your Proxmox host locks up. VMs freeze. SSH stops responding. You reboot and scramble to free space.
The root cause: Docker does not enforce disk limits by default. A single docker run with an over-ambitious application can consume your entire storage pool. This guide shows you how to prevent that with XFS project quotas and Docker’s overlay2 size limit — no third-party tools required.
How Overlay2 Storage Works and Where Quotas Fit
Docker defaults to the overlay2 storage driver on modern Linux systems. It uses a layered filesystem: each image layer is a read-only lower directory, and the container’s writable layer is an upper directory mounted via overlayfs. All container writes land in /var/lib/docker/overlay2/<container-id>/diff.
The backing filesystem for /var/lib/docker matters. On ext4, overlay2 has no built-in per-container size limit. On XFS with project quotas enabled (prjquota), Docker can apply a global size cap per container through the overlay2.size storage option. Each container gets its own XFS project ID automatically, and Docker sets the quota at container creation time.
This is not virtual disk thin provisioning — it is a hard filesystem quota. When a container hits the limit, writes fail with ENOSPC (No space left on device) instead of exhausting the host disk.
Checking Your Current Docker Backing Filesystem
Before making changes, verify your setup:
|
|
If the type column shows xfs, you are on XFS. If ext4, you can still use XFS project quotas on separate data mounts (see the volume section below), but the global overlay2.size option only works with XFS backing the Docker root.
|
|
Look for prjquota or pquota in the mount options. If missing you can either remount or (cleaner) set up a dedicated XFS partition.
Preparing XFS with Project Quotas
If your Docker root is on ext4 or XFS without quotas, the cleanest approach is a dedicated partition. Assuming a spare disk or partition at /dev/sdb1:
|
|
Verify quotas are active:
|
|
The output should show Project quotas on and Enforcement ON.
Setting a Global Container Size Cap
With XFS project quotas active, add the overlay2.size storage option to Docker’s daemon configuration:
|
|
The 10G value is the hard limit per container — adjust based on your workload and available disk. A database container might need 20-50G. A simple web app might be fine with 2G.
Restart Docker and confirm:
|
|
Look for Size: 10G under the Storage Driver section. Now run a test:
|
|
The root filesystem inside the container will show the 10G size. Try to exceed it:
|
|
The write will fail around the 10G mark instead of filling your host disk.
Important: The size cap is set at container creation time and covers the union filesystem (writable layer + image layers). If your image is 8G and the cap is 10G, the container only has 2G of writable space. Plan accordingly.
Per-Container and Per-Service Volume Quotas with XFS
The overlay2.size is a global default — Docker does not support --storage-opt size= at docker run or compose level. For granular control per service, use XFS project quotas on bind-mounted data directories.
This is the pattern for databases, media libraries, and log-heavy containers:
|
|
Now reference these in your Docker Compose file with bind mounts:
|
|
Any writes these containers make to their bind mounts hit the XFS project quota. When Jellyfin’s media cache hits 100G, writes fail — but the rest of the host stays operational.
Applying Quotas to Named Docker Volumes
If you prefer named volumes over bind mounts, create them with a device path and apply the quota to the backing directory:
|
|
In compose, reference it directly:
|
|
Monitoring Quota Usage
Track your quotas and container disk usage regularly:
|
|
Add the threshold check to a cron job or systemd timer for automatic notifications.
Caveats and Limitations
- overlay2.size requires XFS with prjquota — it does not work on ext4, btrfs, or ZFS. For ZFS, use per-dataset quotas instead.
- Non-Linux platforms — Docker Desktop on macOS or Windows does not support this option.
- Bind mounts are not governed by overlay2.size — any directory mounted with
-v /host/path:/container/pathbypasses the overlay2 quota entirely. This is why XFS project quotas on the host are necessary for data-heavy workloads. - Existing containers are not retroactively limited — the overlay2.size option only applies to containers created after the daemon change.
- Image layers count toward the cap — a 5G base image leaves only 5G of writable space with a 10G global limit.
Summary
One runaway container should never take down your entire homelab. XFS project quotas combined with Docker’s overlay2.size option give you hard storage limits that are built into the kernel — no agent, no sidecar, no overlay filesystem hack.
Start with a global 10G cap in daemon.json, then apply per-service project quotas to directories housing your largest data stores. Add a cron-based monitoring check and you have a zero-maintenance storage safety net that works whether you run five containers or fifty.
The disk will fill gracefully — one container at a time — and you will know exactly which one to blame.