Introduction

Docker simplifies application deployment by packaging applications and their dependencies into portable containers. However, without proper storage management, Docker environments can gradually consume significant disk space, causing application builds to fail, deployments to stall and production services to become unstable.

One particularly common issue occurs when organisations continuously rebuild and redeploy Docker images without removing unused versions.

A Linux server with a 100 GB or larger root filesystem can unexpectedly reach 90% disk utilisation even when its running containers appear relatively small.

In this article, we explore a real-world Docker storage troubleshooting scenario, explain how to identify the actual cause of excessive disk consumption, and demonstrate safe cleanup and prevention strategies.

1. Understanding Docker Disk Space Consumption

Docker uses several storage components that consume disk space independently.

Docker componentDescriptionCommon storage issue
ImagesRead-only layers containing applications and dependenciesHistorical versions accumulate
ContainersRunning or stopped instances with writable layersTemporary files accumulate
Build cacheReusable intermediate build dataCache grows after repeated builds
VolumesPersistent application and database dataDatabases and backups expand
Container logsApplication output collected by DockerLogs grow without rotation
OverlayFSDocker's layered filesystem storageOld image layers occupy the root disk

On Linux installations, Docker typically stores its data in:

/var/lib/docker

Unless Docker is configured to use a separate storage location, its images, writable layers, volumes and build data commonly compete with the operating system for disk space.

This makes disk-space monitoring particularly important on production Linux servers.

2. Real-World Scenario: Docker Builds Failing Due to Disk Space

Consider a production Linux server running several containerised services, including:

  • Web applications

  • Backend APIs

  • Background processing workers

  • PostgreSQL databases

  • Redis

  • Reverse proxy services

The environment uses Docker Compose for application deployment, with frequent image rebuilds as new application releases are published.

Eventually, the server reports insufficient disk space during Docker builds.

An initial filesystem check reveals:

df -h

Example output:

Filesystem    Size    Used    Avail    Use%
/dev/root     124G    114G     11G     92%

The root filesystem is already 92% utilised.

Although 11 GB remains available, that may not be sufficient for a Docker build involving large base images, dependency installation and multiple intermediate build stages.

Why Docker Overlay Mounts Can Be Misleading

Running df -h may display several entries similar to:

overlay   124G   114G   11G   92%   /var/lib/docker/overlay2/.../merged
overlay   124G   114G   11G   92%   /var/lib/docker/overlay2/.../merged

These entries do not mean each container consumes 114 GB.

Docker's OverlayFS storage driver presents layered container filesystems using the underlying host filesystem.

Consequently, several overlay mounts may report identical filesystem capacity and utilisation.

The actual disk consumption must be investigated using Docker-specific commands and filesystem analysis tools.

3. Step-by-Step Docker Disk Space Investigation

Step 1: Check Host Disk Utilisation

Begin by examining available filesystem capacity.

df -h

To inspect filesystem usage on the root partition:

df -h /

Next, identify which directories consume the most storage.

sudo du -xhd1 / 2>/dev/null | sort -h

For the Docker data directory:

sudo du -xhd1 /var/lib/docker 2>/dev/null | sort -h

These commands help determine whether the problem is primarily associated with Docker, operating system logs, application files or another directory.

Step 2: Analyse Docker Storage

Docker provides a built-in command to report storage consumption.

sudo docker system df

For detailed information:

sudo docker system df -v

The verbose output includes image sizes, shared layers, unique layer sizes, container associations and volume sizes.

Important values include:

  • Shared size: Layers shared by multiple images.

  • Unique size: Image layers not shared with other images.

  • Containers: Number of containers referencing an image.

  • Reclaimable: Space Docker estimates can be recovered from unused resources.

In the troubleshooting scenario, the investigation uncovered numerous historical images from repeated deployments.

Some application images were larger than 2 GB, and new versions had been generated frequently.

Many older images appeared as:

REPOSITORY    TAG       SIZE      CONTAINERS
<none>        <none>    2.7GB     0
<none>        <none>    1.8GB     0
<none>        <none>    2.7GB     0
<none>        <none>    1.8GB     0

These entries indicated that Docker had retained old, untagged images that were no longer referenced by containers.

Over time, the accumulation of historical image layers became a major storage concern.

Step 3: Check Docker Build Cache

Docker builds can retain intermediate data to accelerate future builds.

To inspect the build cache:

sudo docker buildx du

The output helps establish whether BuildKit cache is responsible for storage growth.

In this scenario, the reported build cache was effectively zero.

That was an important finding.

Although the disk-space problem occurred during builds, the build cache itself was not the cause. The investigation instead pointed towards accumulated Docker images.

Lesson: A problem that becomes visible during a Docker build is not necessarily caused by Docker build cache.

Step 4: Check Container Writable Layers

Inspect all containers and their writable-layer usage.

sudo docker ps -a --size

Example:

NAME             SIZE
web-service      150MB
api-service      2MB
worker-service   300kB
database         0B

The SIZE field shows the approximate writable-layer size, while virtual size also includes image data.

In the investigated environment, most container writable layers were relatively small.

This reduced the likelihood that temporary files stored inside running containers were the primary cause.

Step 5: Check Docker Container Logs

Docker containers commonly use the json-file logging driver.

Without rotation, log files can grow continuously.

Inspect the largest JSON log files:

sudo find /var/lib/docker/containers \
  -type f -name "*-json.log" \
  -printf '%s %p\n' \
  | sort -nr | head -20 \
  | numfmt --field=1 --to=iec

In the scenario, the largest individual logs were a few hundred megabytes.

Although log management was still necessary, the observed log files did not explain the majority of disk consumption.

Avoid manually deleting Docker-managed log files while containers are running.

Instead, configure proper log rotation.

Step 6: Check Docker Volumes

Docker volumes store persistent data such as database files, uploaded documents and application state.

List volumes:

sudo docker volume ls

Inspect their sizes using:

sudo docker system df -v

For standard local volumes:

sudo du -sh /var/lib/docker/volumes/* \
  2>/dev/null | sort -h

The investigated environment contained PostgreSQL and Redis volumes, but their reported sizes were comparatively small.

Production warning: Never delete Docker volumes without confirming their contents, dependencies and backup status. Removing a database volume can permanently destroy application data.

4. Root Cause: Accumulation of Historical Docker Images

The investigation identified a repeated pattern.

Each application deployment produced new Docker images, while older image versions remained on the server.

For example:

Deployment 1: API 1.8GB + Worker 2.7GB
Deployment 2: API 1.8GB + Worker 2.7GB
Deployment 3: API 1.8GB + Worker 2.7GB
Deployment 4: API 1.8GB + Worker 2.7GB

Although the images could share base layers, each application release could introduce significant new image data.

This happens frequently when organisations:

  • Rebuild images directly on production servers.

  • Repeatedly use the latest image tag.

  • Deploy multiple services several times a day.

  • Retain old images for rollback without a retention policy.

  • Do not include controlled image cleanup in the deployment process.

Why the latest Tag Contributes to Confusion

Consider:

docker build -t company-api:latest .

After rebuilding and tagging the replacement image, the old image may lose its tag and appear as:

<none>:<none>

If it is not referenced by a container, it may become eligible for cleanup.

However, untagged images are not always unused.

A running container may still reference an older image ID after a newer image receives the latest tag.

Therefore, image cleanup must account for container references rather than assuming every untagged image can be deleted indiscriminately.

5. How to Safely Clean Up Docker Disk Space

Before cleanup, inspect your images and containers.

sudo docker image ls -a
sudo docker ps -a
sudo docker system df

Option 1: Remove Unused Dangling Images

The recommended first action for this scenario is:

sudo docker image prune

This removes dangling images that are not associated with containers.

It does not remove Docker volumes and does not stop running containers.

After cleanup, verify filesystem usage:

df -h /

Then confirm running container status:

sudo docker ps

Option 2: Remove Dangling Images Older Than 24 Hours

For controlled cleanup of older dangling images:

sudo docker image prune -f \
  --filter "until=24h"

This helps prevent recently generated image layers from being removed immediately.

Note that age-based cleanup is not a complete rollback-retention strategy.

Option 3: Remove Unused Build Cache

Where build cache is consuming substantial disk space:

sudo docker builder prune

For Buildx-managed cache:

sudo docker buildx prune

Removing the cache may increase the time required for subsequent builds.

In the investigated case, build cache was not the major contributor, so cache cleanup alone would not have solved the problem.

Commands to Avoid Without Proper Assessment

Be cautious with broad cleanup commands:

sudo docker system prune -a --volumes

The -a option expands image cleanup to all unused images, including tagged images. Volume-related cleanup options can also remove persistent storage depending on the command and Docker version.

Do not manually remove Docker's internal directories:

sudo rm -rf /var/lib/docker/overlay2

This can damage Docker's storage metadata and disrupt running workloads.

6. Configure Docker Log Rotation

Even when log files are not the primary cause of a disk-space issue, production environments should limit their growth.

Docker supports log rotation using daemon-level logging configuration.

Edit:

sudo nano /etc/docker/daemon.json

For the json-file logging driver, configure:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  }
}

This configuration limits each newly created container's JSON log files to approximately five files of 20 MB each.

Existing daemon settings must be preserved when making changes.

New logging defaults generally apply to newly created containers, so existing containers need to be recreated to adopt the configuration.

Schedule Docker daemon changes during an appropriate maintenance window.

For new installations, Docker's local logging driver is another option that supports rotation by default.

7. Reduce Docker Image Sizes

Large application images significantly increase the storage required for deployment and rollback.

Consider a background worker image of approximately 2.7 GB.

If deployments frequently produce substantial new layers, historical versions can consume significant storage.

Use Multi-Stage Docker Builds

A multi-stage Dockerfile separates build dependencies from the final application runtime.

Example for a Node.js application:

FROM node:22-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:22-alpine

WORKDIR /app

ENV NODE_ENV=production

COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force

COPY --from=builder /app/dist ./dist

USER node

CMD ["node", "dist/index.js"]

This illustrative Dockerfile assumes the application's compiled output is stored in dist and that runtime dependencies are declared correctly.

Application-specific files and native dependencies may require further adjustments.

Use a .dockerignore File

A .dockerignore file prevents unnecessary local files from entering the build context.

Example:

.git
node_modules
coverage
*.log
.env
.env.*
backups/
tmp/
dist/

Ensure required build artefacts are not excluded accidentally.

Reducing build context size can improve build efficiency and avoid copying unnecessary data into intermediate layers.

8. Establish a Docker Image Retention Policy

Production environments should have a defined image-retention process.

A practical approach includes:

AreaRecommended practice
Production imagesUse immutable version tags or image digests
RollbackPreserve approved previous releases
CleanupRemove unreferenced images under a defined retention policy
LogsConfigure rotation and centralised collection
Build cacheReview periodically and limit excessive accumulation
VolumesProtect persistent data with backups
MonitoringAlert before the filesystem becomes critically full

For example, an organisation might retain the current release and two previous verified releases in its container registry.

On production hosts, preserve any locally required rollback images while removing other unreferenced image data.

A controlled deployment pipeline should verify the new application's health before performing cleanup.

9. Monitor Docker Disk Usage Before It Becomes Critical

A disk-space issue should ideally be detected before it interrupts deployment.

Useful Linux monitoring commands include:

df -h /
sudo docker system df
sudo du -xhd1 /var/lib/docker \
  2>/dev/null | sort -h

Organisations can also integrate filesystem metrics into monitoring platforms such as Azure Monitor, Prometheus or Grafana.

An example operational threshold strategy is:

Disk utilisationOperational response
Below 70%Normal monitoring
70–80%Review storage growth
80–90%Investigate and plan cleanup
Above 90%Urgent capacity assessment
Above 95%Critical alert and immediate remediation

These are illustrative thresholds. Appropriate limits depend on disk size, application growth rate and workload behaviour.

10. Long-Term Recommendation: Separate Builds From Production

One of the most effective ways to reduce Docker storage pressure on production servers is to build images outside the production environment.

A modern deployment process can follow this workflow:

  1. A developer commits code to a Git repository.

  2. A CI/CD pipeline builds the Docker image.

  3. The pipeline scans and tests the image.

  4. The image is published to a private container registry.

  5. The production server pulls the approved image.

  6. Health checks confirm a successful deployment.

  7. A defined retention policy removes unnecessary old images.

Azure Container Registry, GitHub Actions and other CI/CD platforms can support this approach.

Separating image builds from production reduces build-time CPU, memory and temporary disk demands on application hosts.

Production disk usage still requires management, particularly when multiple image versions are retained locally.

11. Key Lessons From This Troubleshooting Scenario

This investigation highlights several important operational lessons.

First, do not assume build cache is responsible simply because a failure occurs during a Docker build. Docker's storage reports may reveal that build cache is negligible.

Second, distinguish container storage from historical image storage. Running containers may have very small writable layers while unused historical images consume substantial disk capacity.

Third, avoid broad cleanup commands on production servers. Databases, persistent volumes and rollback requirements must be considered before removing Docker resources.

Fourth, implement image lifecycle management. Frequent deployments without retention policies can progressively exhaust host storage.

Finally, effective Docker operations require more than simply running containers. Storage monitoring, image optimisation, log management and deployment controls are essential for long-term stability.

Conclusion

Docker disk exhaustion is a common but preventable infrastructure issue.

In this troubleshooting scenario, a Linux server reached approximately 92% disk utilisation. A detailed investigation showed that repeated deployments had left numerous historical Docker images on the host.

The findings pointed to accumulated image data rather than excessive build cache or database-volume growth as the principal Docker-related storage concern.

By using Docker's diagnostic commands, identifying unused images, implementing controlled cleanup and improving deployment practices, organisations can reduce storage risks and improve application reliability.

More importantly, proactive monitoring and automated image lifecycle management can help prevent the same issue from recurring.


Need Help With Docker, DevOps or Cloud Infrastructure?

Onsys Technologies provides professional IT infrastructure, database administration, cloud engineering and managed IT services to organisations looking to improve the reliability, performance and security of their technology platforms.

Our technical services include:

  • Docker and container environment troubleshooting

  • Azure cloud infrastructure and application deployment

  • CI/CD pipeline implementation and optimisation

  • Linux server administration

  • SQL Server, PostgreSQL and Oracle database administration

  • Infrastructure monitoring, performance optimisation and managed IT support

Whether you are experiencing storage issues, deployment failures or infrastructure performance problems, our team can help identify the underlying cause and implement sustainable improvements.

Contact Onsys Technologies: www.onsys.com.au

Onsys Technologies — Cloud, Database and IT Infrastructure Expertise.