How to Remove Old and Unused Docker Images
Docker images have a way of quietly accumulating on your system like digital sediment. Each time you pull a new base image, build a development version, or experiment with a different tag, another layer of storage gets consumed. Before you know it, Docker might be using 50GB or more of your disk space, with images you built months ago still taking up room. The challenge isn't just removing images - it's doing so safely while preserving the ones you actually need for current projects.
This guide will walk you through systematic approaches for identifying, evaluating, and removing unused Docker images while avoiding the frustration of accidentally deleting something important and having to rebuild or re-download it later.
Prerequisites
Before starting your cleanup process, make sure you have:
- Docker installed and running on your system
- Administrative access to run Docker commands
- At least a basic understanding of your current Docker usage
- A backup strategy for any critical custom images
- Time to rebuild images if you accidentally remove something important
If you're managing production systems, coordinate cleanup activities during maintenance windows to avoid disrupting running services.
Understanding Docker Image Types and States
Docker images exist in several different states, and understanding these distinctions helps you make informed decisions about what to remove. Not all unused images are the same, and the cleanup strategy should vary based on the image type and your usage patterns.
Docker Image Lifecycle:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Base Images │───▶│ Tagged Images │───▶│ Running Containers
│ (nginx:latest)│ │ (myapp:v1.2) │ │ (active usage) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Dangling Images │ │ Unused Images │ │ Referenced Images│
│ (no tags) │ │ (no containers) │ │ (containers exist)
└─────────────────┘ └─────────────────┘ └─────────────────┘
Dangling images are intermediate layers that lost their tags during rebuilds. These are usually safe to remove. Unused images have tags but no running containers using them. Referenced images are actively used by containers, even if those containers aren't currently running.
Analyzing Current Docker Storage Usage
Before removing anything, understand what's consuming space on your system. This analysis helps you prioritize cleanup efforts and avoid removing images you might need soon.
Get a Complete Storage Overview
Docker provides built-in tools to analyze storage consumption across all components. This overview shows you exactly where your disk space is going and helps you focus your cleanup efforts on the areas with the most impact.
# Get a comprehensive breakdown of Docker storage usage
docker system df
# Get more detailed information about each component
docker system df -v
The detailed output shows size information for each individual image, container, and volume. Look for patterns like multiple versions of the same application or base images you no longer use.
You'll see output like this:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 45 12 8.2GB 6.1GB (74%)
Containers 23 3 2.1GB 1.8GB (85%)
Local Volumes 8 2 1.2GB 800MB (66%)
Build Cache 0 0 0B 0B
The "RECLAIMABLE" column shows how much space you could potentially free up, giving you a clear target for your cleanup efforts.
Identify Your Largest Images
Large images consume disproportionate amounts of space and are often good candidates for cleanup. Images over 1GB might contain development tools, debugging utilities, or multiple applications that you no longer need.
# List all images sorted by size (largest first)
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" | sort -k3 -hr
# Find images larger than 1GB
docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | grep -E '[0-9]+(\.[0-9]+)?GB'
# Show only image IDs and sizes for large images
docker images --filter "dangling=false" --format "{{.ID}} {{.Repository}}:{{.Tag}} {{.Size}}" | sort -k3 -hr
Pay special attention to development images, which often include build tools and dependencies that production images don't need. These are frequently good candidates for removal or rebuilding with smaller base images.
Selective Image Removal Strategies
Rather than removing everything at once, start with targeted cleanup approaches that minimize risk while maximizing space recovery. This methodical approach helps you learn what's safe to remove before applying more aggressive cleanup strategies.
Remove Dangling Images First
Dangling images are the safest to remove because they're intermediate build layers that are no longer tagged or referenced. These images accumulate during development when you rebuild images multiple times, and removing them rarely causes problems.
# List dangling images to see what will be removed
docker images --filter "dangling=true"
# Remove all dangling images (safest cleanup option)
docker image prune
# Force removal without confirmation prompt
docker image prune -f
Dangling images often result from Docker's build process creating intermediate layers that get replaced during subsequent builds. They serve no purpose and can safely be removed to reclaim space.
Target Images by Age
Older images are often good candidates for removal, especially in development environments where you're constantly building new versions. However, be careful with this approach in production environments where older images might be needed for rollbacks.
# List images older than 30 days with creation dates
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}" | grep -E "$(date -d '30 days ago' '+%Y-%m')"
# Remove images created before a specific date (use with caution)
docker images --format "{{.ID}} {{.CreatedAt}}" | grep "2024-01" | awk '{print $1}' | xargs docker rmi
# Find and list images older than 2 weeks
docker images --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" --filter "before=$(date -d '2 weeks ago' -Iseconds)"
When targeting images by age, consider your deployment patterns. If you deploy weekly, images older than a month might be safe to remove. If you deploy less frequently, you might want to keep older images for potential rollbacks.
Remove Images by Pattern
When you have multiple versions of the same application or base images you no longer use, pattern-based removal helps you clean up systematically while keeping images you still need.
# Remove all versions of a specific application except the latest
docker images myapp --format "{{.Tag}}" | grep -v latest | xargs -I {} docker rmi myapp:{}
# Remove all development/test tagged images
docker images --format "{{.Repository}}:{{.Tag}}" | grep -E ':(dev|test|staging)' | xargs docker rmi
# Remove images from a specific registry you no longer use
docker images --format "{{.Repository}}:{{.Tag}}" | grep "old-registry.company.com" | xargs docker rmi
Pattern-based removal is particularly useful when migrating between different base images or when you've changed your tagging strategy and want to clean up images that follow the old pattern.
Safe Bulk Removal Techniques
When you need to remove multiple images at once, these techniques help you do so safely while avoiding accidentally removing images that containers depend on.
Remove All Unused Images with Safety Checks
The most common bulk removal operation is eliminating all images that no containers are currently using. This approach is more aggressive than removing just dangling images but still maintains safety by preserving images that containers reference.
# See what will be removed before actually removing it
docker image prune -a --filter "until=72h"
# Remove all unused images (images not referenced by containers)
docker image prune -a
# Remove unused images without confirmation prompt
docker image prune -a -f
The -a
flag removes all unused images, not just dangling ones. This includes tagged images that no containers are using. Be cautious with this approach if you have images you want to keep for future use but aren't currently running.
Remove Images with Filters for Precision
Docker's filtering system allows you to target specific subsets of images for removal, giving you more control over bulk operations while maintaining safety through precision.
# Remove images older than 48 hours that are unused
docker image prune -a --filter "until=48h"
# Remove images with specific labels (useful for CI/CD cleanup)
docker image prune -a --filter "label=environment=development"
# Remove unused images from specific repository
docker images --format "{{.Repository}}:{{.Tag}}" | grep "^myapp:" | grep -v latest | xargs docker rmi
Filters are particularly useful in automated environments where you want to remove images based on specific criteria while preserving others. The until
filter is especially valuable for keeping recent images while removing older ones.
Handle Removal Conflicts and Dependencies
Sometimes images can't be removed because containers (even stopped ones) still reference them. Understanding how to identify and resolve these dependencies helps you complete cleanup operations successfully.
# See which containers are using a specific image
docker ps -a --filter "ancestor=myapp:v1.0"
# Remove stopped containers that might be blocking image removal
docker container prune
# Force remove an image even if containers reference it (use with extreme caution)
docker rmi -f myapp:v1.0
# Remove containers and their images together
docker ps -a --filter "ancestor=myapp:v1.0" -q | xargs docker rm
docker rmi myapp:v1.0
Be very careful with force removal (-f
flag) as it can leave containers in an inconsistent state. It's usually better to remove or update the containers first, then remove the images.
Automated Cleanup Strategies
Manual cleanup works well for occasional maintenance, but automated approaches help prevent storage issues from developing in the first place. Setting up regular cleanup routines saves time and prevents your development environment from becoming cluttered.
Schedule Regular Cleanup Tasks
Automated cleanup prevents the accumulation of unused images and reduces the need for major cleanup sessions. The key is finding the right balance between maintaining recent images for quick access and removing old ones to save space.
# Create a cleanup script for regular execution
#!/bin/bash
# docker-cleanup.sh
echo "Starting Docker cleanup at $(date)"
# Remove dangling images
echo "Removing dangling images..."
docker image prune -f
# Remove images older than 7 days
echo "Removing images older than 7 days..."
docker image prune -a --filter "until=168h" -f
# Remove unused containers
echo "Removing unused containers..."
docker container prune -f
# Show remaining usage
echo "Storage usage after cleanup:"
docker system df
echo "Cleanup completed at $(date)"
Add this script to your cron jobs for regular execution:
# Add to crontab for weekly cleanup (run at 2 AM every Sunday)
0 2 * * 0 /path/to/docker-cleanup.sh >> /var/log/docker-cleanup.log 2>&1
Smart Cleanup Based on Usage Patterns
Instead of removing images based solely on age, smart cleanup considers usage patterns and preserves images that you're likely to need again soon. This approach maintains convenience while managing storage efficiently.
#!/bin/bash
# smart-cleanup.sh
# Keep images that have been used recently (containers created from them)
RECENT_IMAGES=$(docker ps -a --format "{{.Image}}" | sort | uniq)
# Get all images
ALL_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}")
# Find images not used recently
for image in $ALL_IMAGES; do
if ! echo "$RECENT_IMAGES" | grep -q "$image"; then
echo "Candidate for removal: $image"
# Add additional logic here for removal decisions
fi
done
This script provides a foundation for building smarter cleanup logic that considers your specific usage patterns.
CI/CD Integration for Build Cleanup
In CI/CD environments, images accumulate rapidly during builds and testing. Integrating cleanup into your pipeline prevents build servers from running out of space and keeps environments clean.
# Example GitHub Actions cleanup step
- name: Cleanup Docker Images
run: |
# Remove images older than 24 hours (build artifacts)
docker image prune -a --filter "until=24h" -f
# Keep only the last 3 builds of each application
docker images --format "{{.Repository}}:{{.Tag}}" | \
grep "^myapp:" | \
sort -V | \
head -n -3 | \
xargs -r docker rmi
Troubleshooting Common Cleanup Issues
Cleanup operations don't always go smoothly. Understanding common problems and their solutions helps you resolve issues quickly and avoid leaving your Docker environment in an inconsistent state.
Resolving "Image is Being Used" Errors
One of the most common cleanup problems occurs when you try to remove an image that containers are still referencing, even if those containers aren't running. This protection prevents you from accidentally breaking container configurations.
# Find out which containers are using a specific image
docker ps -a --filter "ancestor=problematic-image:tag"
# See detailed information about container dependencies
docker inspect problematic-image:tag | grep -A 10 -B 10 "Container"
# Remove all containers using the image before removing the image
docker ps -a --filter "ancestor=problematic-image:tag" -q | xargs docker rm
docker rmi problematic-image:tag
If you get errors about containers still using an image, systematically removing those containers usually resolves the issue. However, make sure you won't need those containers later.
Handling Large Numbers of Images
When you have hundreds or thousands of images, standard removal commands might timeout or consume too much memory. Breaking the operation into smaller batches prevents these problems.
# Remove images in batches to avoid overwhelming the system
docker images -q | head -20 | xargs docker rmi
# Use parallel processing for faster cleanup (but more resource intensive)
docker images -q | xargs -n 1 -P 4 docker rmi
# Remove images one at a time with error handling
for image_id in $(docker images -q); do
echo "Removing $image_id..."
docker rmi "$image_id" || echo "Failed to remove $image_id"
done
Batch processing is particularly important on systems with limited memory or when cleaning up very large numbers of images.
Recovery from Cleanup Mistakes
Sometimes you accidentally remove an image you needed. Understanding recovery options helps you get back to a working state quickly without starting from scratch.
# Check if the image is available in a registry
docker search myapp
# Pull the image again if it was from a registry
docker pull myapp:v1.0
# Rebuild the image if it was custom built
docker build -t myapp:v1.0 .
# Check Docker's recycle bin (if available on your system)
docker images --filter "dangling=true" | grep "some-identifier"
For critical custom images, maintain a registry or backup strategy so you can recover from accidental deletions without rebuilding from source.
Storage Optimization Best Practices
Beyond cleanup, optimizing how you build and manage images prevents excessive storage consumption and reduces the need for frequent cleanup operations.
Build Smaller Images
The best cleanup strategy is generating less waste in the first place. Building smaller, more efficient images reduces storage requirements and speeds up both builds and deployments.
# Use multi-stage builds to reduce final image size
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["npm", "start"]
Multi-stage builds can reduce image sizes by 60-80% by excluding build tools and intermediate artifacts from the final image.
Implement Image Lifecycle Management
Establish policies for how long to keep different types of images and automate enforcement of those policies. This approach prevents accumulation while preserving images you actually need.
# Example lifecycle policy implementation
#!/bin/bash
# image-lifecycle.sh
# Keep production images for 90 days
docker images --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" | \
grep ":prod" | \
grep "$(date -d '90 days ago' '+%Y-%m')" | \
awk '{print $1}' | \
xargs docker rmi
# Keep development images for 7 days
docker images --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" | \
grep ":dev" | \
grep "$(date -d '7 days ago' '+%Y-%m')" | \
awk '{print $1}' | \
xargs docker rmi
# Keep staging images for 30 days
docker images --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" | \
grep ":staging" | \
grep "$(date -d '30 days ago' '+%Y-%m')" | \
awk '{print $1}' | \
xargs docker rmi
Monitor Storage Trends
Regular monitoring helps you understand storage patterns and adjust cleanup strategies before storage becomes a problem. Tracking trends also helps you identify applications or workflows that generate excessive images.
# Create a monitoring script for storage trends
#!/bin/bash
# storage-monitor.sh
LOG_FILE="/var/log/docker-storage.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
# Log current storage usage
echo "$DATE - Storage usage:" >> $LOG_FILE
docker system df >> $LOG_FILE
# Log image count by repository
echo "$DATE - Image counts:" >> $LOG_FILE
docker images --format "{{.Repository}}" | sort | uniq -c | sort -nr >> $LOG_FILE
echo "---" >> $LOG_FILE
Run this script daily to build a picture of your storage usage patterns and identify when cleanup is needed.
Managing Docker image storage effectively requires a combination of understanding, systematic approaches, and automation. Start with safe cleanup methods like removing dangling images, then gradually move to more comprehensive strategies as you become comfortable with the impact. The key is developing a routine that keeps your storage usage reasonable without accidentally removing images you need for current or future projects.
Found an issue?