Skip to content

Instantly share code, notes, and snippets.

@cumulus13
Created December 22, 2025 09:25
Show Gist options
  • Select an option

  • Save cumulus13/ef3bcacd1fb436abf2cc9eae7df0f9a1 to your computer and use it in GitHub Desktop.

Select an option

Save cumulus13/ef3bcacd1fb436abf2cc9eae7df0f9a1 to your computer and use it in GitHub Desktop.
Prune Docker images with 'None' tag.
#!/usr/bin/env python3
#File: dprune.py
## Author: Hadi Cahyadi <cumulus13@gmail.com>
# Date: 2025-12-22
# Description: Prune Docker images with 'None' tag.
# License: MIT
import subprocess
from rich.console import Console
from rich_argparse import RichHelpFormatter, _lazy_rich as rr
from typing import ClassVar
from rich.progress import Progress
import argparse
import sys
import time
import re
console = Console()
class CustomRichHelpFormatter(RichHelpFormatter):
"""A custom RichHelpFormatter with modified styles."""
styles: ClassVar[dict[str, rr.StyleType]] = {
"argparse.args": "bold #FFFF00", # Changed from cyan
"argparse.groups": "#AA55FF", # Changed from dark_orange
"argparse.help": "bold #00FFFF", # Changed from default
"argparse.metavar": "bold #FF00FF", # Changed from dark_cyan
"argparse.syntax": "underline", # Changed from bold
"argparse.text": "white", # Changed from default
"argparse.prog": "bold #00AAFF italic", # Changed from grey50
"argparse.default": "bold", # Changed from italic
}
def colorize_docker_output(out):
# Color <none> (first and second on each line)
def replace_none(match):
return "[#FF5500]<none>[/]"
# Color image_id (3rd column, usually 12 hex characters)
def replace_image_id(match):
return f"[#00FFFF]{match.group(0)}[/]"
# Color the time (eg: '47 hours ago', '2 weeks ago', etc.)
def replace_time(match):
return f"[#AA55FF]{match.group(0)}[/]"
# Color the size (eg: '319MB', '551MB', etc)
def replace_size(match):
return f"[#5555FF]{match.group(0)}[/]"
lines = out.splitlines()
colored_lines = []
for line in lines:
# Color <none> (max 2 times per line)
line = re.sub(r"<none>", replace_none, line, count=2)
# Color image_id (12+ hex, 3rd column)
line = re.sub(r"\b[a-f0-9]{12,}\b", replace_image_id, line, count=1)
# Color the time (eg: '47 hours ago', '2 weeks ago', etc.)
line = re.sub(r"\b\d+\s+\w+\s+ago\b", replace_time, line)
# Color the size (eg: '319MB', '551MB', etc)
line = re.sub(r"\b\d+MB\b", replace_size, line)
colored_lines.append(line)
return "\n".join(colored_lines)
def dprune(quiet=False):
cmd = "docker image ls -a | grep -i None"
try:
result = subprocess.run(cmd, shell=True, check=True, text=True, capture_output=True)
out, err = result.stdout, result.stderr
if out:
console.print("\n[bold #FFFF00]Docker images with[/] [white on #0000FF]'None'[/] [bold #FFFF00]tag:[/]")
# console.print(out, "\n")
console.print(colorize_docker_output(out), "\n")
image_ids = [line.split()[2] for line in out.splitlines()]
if not quiet:
q = console.input("[bold #00FFFF]Do you want to remove these images? (y/n): [/]")
print("\n")
if q.lower() != 'y':
console.print("[white on #550000]Exiting without removing images.[/]")
return
with Progress() as progress:
emoji = "🐳"
task = progress.add_task(f"{emoji} [bold #FFFF00]remove[/]: [bold #00FFFF]{image_ids[0]}[/]", total=len(image_ids))
for idx, image_id in enumerate(image_ids):
remove_cmd = f"docker rmi {image_id}"
try:
subprocess.run(remove_cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
progress.update(task, description=f"{emoji} [bold #FFFF00]remove[/]: [bold #00FFFF]{image_id}[/]")
progress.advance(task)
# time.sleep(0.1)
except subprocess.CalledProcessError as e:
progress.console.print(f"[white on #005500]Failed to remove image:[/] [bold #00FFFF]{image_id}[/] - [white on red]{e.stderr.decode().strip()}[/]")
# hide progress bar
progress.stop()
print("\n")
console.print(f"[white on #0000FF]Done removing images.[/]")
else:
console.print("[black on #FFFF00]No Docker images with 'None' tag found.[/]")
except subprocess.CalledProcessError as e:
console.print(f"[white on red]An error occurred:[/] [white on blue]{e.stderr}[/]")
except Exception as e:
console.print(f"[white on red]An unexpected error occurred:[/] [[white on blue]{str(e)}[/]")
def usage():
parser = argparse.ArgumentParser(
prog="dprune",
description="Prune Docker images with 'None' tag.",
formatter_class=CustomRichHelpFormatter,
)
parser.add_argument(
"-v", "--version",
action="version",
version="%(prog)s 1.0",
help="Show the version of the program."
)
parser.add_argument(
"-q", "--quiet",
action="store_true",
help="Suppress confirmation prompt before removing images."
)
parser.add_argument(
"-s", "--setup",
action="store_true",
help="Setup dprune command in the system."
)
if len(sys.argv) == 1:
parser.print_help()
dprune()
else:
args = parser.parse_args()
if args.setup:
p = subprocess.run("pip install rich rich-argparse", shell=True)
out, err = p.stdout, p.stderr
if p.returncode != 0:
console.print(f"[white on red]Failed to install dependencies:[/] [white on blue]{err}[/]")
sys.exit(1)
console.print("[white on #005500]Setup completed. You can now use 'dprune' command.[/]")
else:
dprune(quiet=True)
if __name__ == "__main__":
usage()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment