#!/bin/bash # cleanup.docker.images.sh # # Removes old Docker images, keeping only the newest image per repository. # Images are sorted by creation date, and only the most recent per repo is retained. # # Usage: # ./cleanup.docker.images.sh [--dryrun] # # Options: # --dryrun Show what would be deleted without actually deleting # # Returns: # 0 on success # 1 on error set -euo pipefail DRYRUN=false # Parse arguments for arg in "$@"; do case $arg in --dryrun) DRYRUN=true shift ;; --help|-h) echo "Usage: $0 [--dryrun]" echo "" echo "Removes old Docker images, keeping only the newest per repository." echo "" echo "Options:" echo " --dryrun Show what would be deleted without actually deleting" echo " --help Show this help message" exit 0 ;; *) echo "Unknown option: $arg" echo "Use --help for usage information" exit 1 ;; esac done # Get images to delete: all but the newest per repository # Format: repository|tag|id|created # Sort by repo, then by date descending (newest first) # Skip the first (newest) for each repo, output the rest get_images_to_delete() { docker images --format '{{.Repository}}|{{.Tag}}|{{.ID}}|{{.CreatedAt}}' | \ grep -v '' | \ sort -t'|' -k1,1 -k4r | \ awk -F'|' 'seen[$1]++ {print $1"|"$2"|"$3}' } # Get unique image IDs from the list get_unique_ids() { awk -F'|' '{print $3}' | sort -u } echo "Scanning Docker images..." echo "" IMAGES_TO_DELETE=$(get_images_to_delete) if [ -z "$IMAGES_TO_DELETE" ]; then echo "No old images to clean up. Each repository has only one image." exit 0 fi # Count images IMAGE_COUNT=$(echo "$IMAGES_TO_DELETE" | wc -l | tr -d ' ') UNIQUE_IDS=$(echo "$IMAGES_TO_DELETE" | get_unique_ids) UNIQUE_COUNT=$(echo "$UNIQUE_IDS" | wc -l | tr -d ' ') if [ "$DRYRUN" = true ]; then echo "=== DRY RUN MODE ===" echo "The following images would be deleted ($IMAGE_COUNT tags, $UNIQUE_COUNT unique images):" echo "" printf "%-50s %-35s %s\n" "REPOSITORY" "TAG" "IMAGE ID" printf "%-50s %-35s %s\n" "----------" "---" "--------" echo "$IMAGES_TO_DELETE" | while IFS='|' read -r repo tag id; do printf "%-50s %-35s %s\n" "$repo" "$tag" "$id" done echo "" echo "Run without --dryrun to delete these images." else echo "Deleting $IMAGE_COUNT image tags ($UNIQUE_COUNT unique images)..." echo "" DELETED=0 FAILED=0 for id in $UNIQUE_IDS; do if docker rmi "$id" 2>/dev/null; then ((DELETED++)) || true else # Try force removal if normal removal fails if docker rmi -f "$id" 2>/dev/null; then ((DELETED++)) || true else echo "Warning: Could not delete image $id (may be in use)" ((FAILED++)) || true fi fi done echo "" echo "Cleanup complete: $DELETED deleted, $FAILED failed/skipped" fi