Back to articles
Technology Insight

Automating VPS Storage Optimization: Cleaning Docker Build Cache and Dangling Images via Systemd Timers

June 4, 2026

Introduction: The Hidden Storage Cost of Modern DevOps

In the era of microservices and continuous deployment, Docker has become the gold standard for containerization. It allows development teams to package applications with all their dependencies, ensuring consistency across environments. However, this convenience comes with a hidden operational cost: rapid storage consumption. When deploying Docker containers on a Virtual Private Server (VPS), disk space is often a constrained and expensive resource.

During iterative deployment cycles, Docker continuously generates intermediate layers, build caches, and untagged images. Over time, these artifacts accumulate, quietly consuming gigabytes of storage until the host system encounters a catastrophic "No space left on device" error. This can crash critical database containers, halt CI/CD pipelines, and cause unexpected downtime.

While manual execution of docker system prune temporarily mitigates the issue, it relies on human intervention and is prone to neglect. Relying on traditional Cron jobs is an option, but it lacks the sophisticated logging, dependency management, and error handling required for enterprise-grade infrastructure. This comprehensive guide details a more resilient solution: automating Docker storage optimization using native Linux Systemd Services and Systemd Timers.

Understanding the Culprits: Dangling Images and Build Cache

Before implementing an automation strategy, it is critical to understand precisely what types of data are draining your VPS storage and why they are safe to remove.

What Are Dangling Images?

A dangling image is a layer that is no longer associated with any tagged image. This typically occurs when you build a new version of an image with the exact same tag as an existing one (e.g., web-app:latest). Docker replaces the tag on the new image, leaving the older layers untagged. In CLI outputs, these appear as :. They occupy substantial disk space while serving zero functional purpose for running containers.

The Proliferation of Docker Build Cache

Introduced natively with BuildKit, the Docker build cache stores the results of previous build steps to accelerate subsequent image compilations. While invaluable during active development, on a production VPS or a self-hosted CI/CD runner, these cache layers quickly become stale. If your application code changes frequently, older cache layers remain stored indefinitely, creating a massive storage deficit.

Why Choose Systemd Timers Over Traditional Cron Jobs?

For decades, Cron has been the default task scheduler in Unix-like systems. However, modern Linux distributions utilize Systemd as their init system, which provides a vastly superior alternative for task scheduling via Systemd Timers. The architectural advantages for DevOps professionals include:

  • Centralized Logging: Every execution, success, or failure is natively logged to journalctl. There is no need to manually redirect stdout and stderr to obscure log files.
  • Dependency Control: Systemd ensures that the maintenance task only fires if the Docker daemon is active and responsive (using the After=docker.service directive).
  • Resource Control (Cgroups): You can restrict the CPU and memory consumption of the cleanup process to prevent performance degradation on production workloads.
  • Flexible Scheduling: Timers support monotonic events (e.g., "run 15 minutes after boot") alongside standard calendar schedules.

Step-by-Step Implementation Guide

Let us walk through the process of creating a production-ready automation system on your Ubuntu or Debian-based VPS.

Step 1: Creating the Shell Script for Safe Pruning

First, we need to create a dedicated shell script that executes the optimization commands sequentially. It is vital to use the correct flags to prevent deleting active data.

sudo mkdir -p /usr/local/bin/
sudo nano /usr/local/bin/docker-cleanup.sh

Paste the following script into the file:

#!/bin/bash
set -euo pipefail

echo "=== Starting Docker Storage Optimization: $(date) ==="

# 1. Clear dangling images safely
echo "Removing dangling images..."
docker image prune -f

# 2. Clear BuildKit build cache older than 24 hours
echo "Pruning stale build cache..."
docker builder prune -f --filter "until=24h"

echo "=== Docker Storage Optimization Completed Successfully ==="

Make the script executable to allow system processes to run it:

sudo chmod +x /usr/local/bin/docker-cleanup.sh

Note: We intentionally avoid the -a (all) flag in docker image prune to ensure that unused, non-dangling images (such as cached base images used for emergency rollbacks) are preserved.

Step 2: Defining the Systemd Service Unit

Next, we construct the Systemd service unit configuration file. This file describes what needs to be executed.

sudo nano /etc/systemd/system/docker-cleanup.service

Insert the following configuration:

[Unit]
Description=Automated Docker Storage Optimization
After=docker.service
Wants=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-cleanup.sh
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Step 3: Creating the Systemd Timer

Now, we create the corresponding timer unit file which defines when the service should be executed. We will configure it to run weekly at a low-traffic period (e.g., Sunday at 3:00 AM).

sudo nano /etc/systemd/system/docker-cleanup.timer

Add the following lines:

[Unit]
Description=Run Automated Docker Storage Optimization Weekly

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

The Persistent=true flag is a crucial feature: if the VPS is powered off or rebooting when the timer was scheduled to trigger, Systemd will execute the service immediately upon the next boot, ensuring consistency.

Step 4: Activating and Testing the System

With all configuration files in place, reload the Systemd manager configuration, enable the timer, and verify its active status.

# Reload daemon configuration
sudo systemctl daemon-reload

# Enable and start the timer
sudo systemctl enable --now docker-cleanup.timer

# Verify timer status
sudo systemctl status docker-cleanup.timer

To ensure that the script and service operate correctly without waiting until Sunday, trigger the service manually:

sudo systemctl start docker-cleanup.service

Monitoring and Verification

System administrators must verify that automated tasks execute smoothly. You can monitor the historical execution logs and output of your script at any time using journalctl:

sudo journalctl -u docker-cleanup.service -n 50 --no-pager

This command outputs the specific lines logged by the script execution, displaying precisely how many images or cache layers were cleared and confirming system health.

Conclusion: Long-term Peace of Mind

Automating infrastructure maintenance is a core tenet of efficient DevOps. By configuring a native Systemd Timer to clean up your VPS's Docker environment, you insulate your business applications against sudden disk-space exhaustion. Storage optimization transfers operational management from a reactive crisis response to an invisible, proactive background mechanism, ensuring your digital infrastructure remains lean, cost-efficient, and highly available.