How to Set Docker Container Storage Limits Using storage-opt with XFS and Overlay2

Introduction: Why You Need Storage Limits in Docker

By default, Docker does not enforce a strict storage limit on a container's writable layer. Each container has its own writable layer, which resides on the host's disk — usually under /var/lib/docker.

That means containers can continue writing data until they consume the entire host filesystem. If left unchecked, this can create serious problems:

Risks of Not Setting Storage Limits

  1. Disk Space Exhaustion

    • Containers can fill up the host’s disk.
    • New containers may fail to start.
    • Even system processes — including root login — may fail.
  2. Performance Degradation

    • Disk I/O latency increases as storage fills.
    • Docker’s overlay storage driver slows down with a bloated writable layer.
  3. Data Loss & Instability

    • Applications may crash or behave unpredictably.
    • File writes can fail, causing corruption or inconsistent states.

To prevent this, Docker provides the storage-opt option, which allows you to enforce per-container storage limits. This guide walks you through configuring it from scratch with overlay2 and XFS.


How Docker Enforces Storage Limits with storage-opt

When you run a container with --storage-opt size=1G, Docker doesn’t apply the limit directly. Instead, it depends on:

  • Storage Driver: For most Linux systems, this is overlay2.
  • Filesystem Support: XFS with project quotas (pquota) enabled.

Only when both conditions are met can Docker successfully apply storage-opt.

To know more about other supported storage drivers and backing filesystems, see the official Docker storage drivers documentation.


Step-by-Step Guide: Enabling storage-opt for Docker

Let’s go through the process of configuring storage limits for Docker containers.

Step 1: Configure the Backing Filesystem

In this example, we’ll create a dedicated disk for Docker data, using LVM with an XFS filesystem.

 1# I intend to use 'sdb' for docker. 
 2$ lsblk | grep sdb
 3sdb      8:16   0   50G  0 disk 
 4
 5# Create a physical volume
 6$ sudo pvcreate /dev/sdb
 7
 8# Create a volume group
 9$ sudo vgcreate docker_vg /dev/sdb
10
11# Create a logical volume
12$ sudo lvcreate -l 100%FREE -n docker_lv01 docker_vg
13
14# Format the LV with XFS filesystem
15$ sudo mkfs.xfs /dev/docker_vg/docker_lv01 

Step 2: Mount Docker Data Directory

Now we’ll mount the newly created filesystem at /var/lib/docker.

  • First, fetch the UUID of the logical volume:
1$ blkid /dev/docker_vg/docker_lv01 
2/dev/docker_vg/docker_lv01: UUID="38266c4b-da6b-4df8-a5c4-b0f1e0342194" BLOCK_SIZE="512" TYPE="xfs"
  • Add the following entry to /etc/fstab for persistence:
1UUID=38266c4b-da6b-4df8-a5c4-b0f1e0342194    /var/lib/docker    xfs    defaults    0    0
  • Then, mount and verify:
1$ sudo mkdir /var/lib/docker
2$ sudo mount -a 
3$ df -hT

You should see /var/lib/docker mounted with XFS.

Step 3: Install Docker

Docker can be installed via:

  • APT repository
  • Manual package
  • Convenience script

For detailed instructions, refer to my Docker installation guide.

Step 4: Verify the Storage Driver

Check which storage driver Docker is using:

1$ docker info | grep "Storage Driver"
2 Storage Driver: overlay2

If you see overlay2, you’re good to proceed.

Step 5: Run a Test Container with Storage Limit

Try running an Nginx container with a 1GB storage limit:

1$ docker run -d --storage-opt size=1G nginx

You may encounter this error:

1docker: Error response from daemon: --storage-opt is supported only for overlay over xfs with 'pquota' mount option

This means you need to enable project quotas.

Step 6: Enable Project Quotas (pquota)

Check current mount options:

1$ findmnt /var/lib/docker
2TARGET          SOURCE                            FSTYPE OPTIONS
3/var/lib/docker /dev/mapper/docker_vg-docker_lv01 xfs    rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota

If you don’t see prjquota (only noquota), update /etc/fstab with pquota :

1UUID=38266c4b-da6b-4df8-a5c4-b0f1e0342194    /var/lib/docker    xfs    rw,pquota    0    1

Apply the changes:

1sudo systemctl stop docker
2sudo umount /var/lib/docker
3sudo mount -a 
4sudo systemctl start docker

Confirm with:

1$ findmnt /var/lib/docker
2/var/lib/docker ... xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,prjquota

Now pquota is enabled.

Step 7: Test Storage Limit Enforcement

Run the container again:

1$ docker run --name nginx -d --storage-opt size=1G nginx

Verify with:

1$ docker inspect nginx | jq -r '.[0].HostConfig.StorageOpt'
2{
3  "size": "1G"
4}

Step 8: Validate by Writing Data Inside Container

Exec into the container and try writing files:

 1$ docker exec -it nginx bash
 2root@10beafafdbce:/# df -kh
 3Filesystem                         Size  Used Avail Use% Mounted on
 4overlay                            1.0G   20K  1.0G   1% /
 5tmpfs                               64M     0   64M   0% /dev
 6shm                                 64M     0   64M   0% /dev/shm
 7/dev/mapper/docker_vg-docker_lv01   50G  588M   50G   2% /etc/hosts
 8tmpfs                              5.9G     0  5.9G   0% /proc/acpi
 9tmpfs                              5.9G     0  5.9G   0% /proc/scsi
10tmpfs                              5.9G     0  5.9G   0% /sys/firmware
11tmpfs                              5.9G     0  5.9G   0% /sys/devices/virtual/powercap
12
13# Try writing a 1.5GB file
14fallocate -l 1.5G myfile
15# Output: No space left on device
16
17# Write a smaller file within limits
18fallocate -l 0.5G myfile
19
20root@10beafafdbce:/# df -kh
21Filesystem                         Size  Used Avail Use% Mounted on
22overlay                            1.0G  513M  512M  51% /
23tmpfs                               64M     0   64M   0% /dev
24shm                                 64M     0   64M   0% /dev/shm
25/dev/mapper/docker_vg-docker_lv01   50G  1.1G   49G   3% /etc/hosts
26tmpfs                              5.9G     0  5.9G   0% /proc/acpi
27tmpfs                              5.9G     0  5.9G   0% /proc/scsi
28tmpfs                              5.9G     0  5.9G   0% /sys/firmware
29tmpfs                              5.9G     0  5.9G   0% /sys/devices/virtual/powercap

This proves the limit works.


Bonus: Check Storage Quotas from Linux

Docker relies on the Linux kernel to enforce quotas. You can check the project quota assigned to the container:

 1# Get container ID
 2$ docker ps
 3e3c3628826d2   nginx     "/docker-entrypoint.…"   24 minutes ago   Up 24 minutes   80/tcp    nginx
 4
 5# Find overlay UpperDir
 6$ docker inspect -f '{{ .GraphDriver.Data.UpperDir }}' e3c3628826d2
 7/var/lib/docker/overlay2/ff7f2c5a832681c29ed86cf41ad84f14aac2bffe288d4553712b8d8b172847fd/diff 
 8
 9# Check project ID
10$ sudo xfs_io -c "lsproj" /var/lib/docker/overlay2/ff7f2c5a832681c29ed86cf41ad84f14aac2bffe288d4553712b8d8b172847fd/diff 
11projid = 3
12
13# Report quota usage
14$ sudo xfs_quota -x -c "report -h" /var/lib/docker | grep 3
15#3         512.0M     1G     1G  00 [------] 

This shows how much storage the container has actually consumed at the filesystem level.


Conclusion

Setting storage limits in Docker with storage-opt is essential for:

  • Preventing containers from consuming all disk space.
  • Maintaining host performance and stability.
  • Ensuring data integrity and workload reliability.

By using overlay2 + XFS with project quotas, you can confidently enforce per-container storage quotas in production.

Now you have a fully working setup to protect your Docker host from runaway containers.