Skip to content

Instantly share code, notes, and snippets.

@FNGarvin
Created February 15, 2026 02:21
Show Gist options
  • Select an option

  • Save FNGarvin/536ca974e3d2c17e1d961d7c9d6a8ee5 to your computer and use it in GitHub Desktop.

Select an option

Save FNGarvin/536ca974e3d2c17e1d961d7c9d6a8ee5 to your computer and use it in GitHub Desktop.
Podman Overlay Snoop: Maps physical hex-folders to containers/images with accurate size reporting.
#!/usr/bin/env python3
"""
Author: FNGarvin
License: MIT
Usage: snoop_overlays
Maps every folder in the overlay directory to Podman objects with
accurate size reporting using a robust find-sum method.
"""
import os
import sys
import json
import subprocess
import pwd
# --- Configuration ---
def get_current_username():
try:
return os.environ.get("SUDO_USER") or pwd.getpwuid(os.getuid()).pw_name
except (KeyError, AttributeError):
return os.environ.get("USER") or "user"
REAL_USER = get_current_username()
OVERLAY_PATH = os.path.expanduser(f"~{REAL_USER}/.local/share/containers/storage/overlay")
def format_size(size_bytes):
"""Converts bytes to human readable format."""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_bytes < 1024.0:
return f"{size_bytes:3.1f} {unit}"
size_bytes /= 1024.0
return f"{size_bytes:3.1f} PB"
def get_folder_size(path):
"""
Calculates folder size by summing file bytes via find -printf.
This is the most resilient method for rootless overlay storage.
"""
try:
# Sums %s (size in bytes) of all files, ignoring errors
cmd = f"find '{path}' -printf '%s\\n' 2>/dev/null | awk '{{sum += $1}} END {{print sum}}'"
out = subprocess.check_output(cmd, shell=True, text=True).strip()
return int(out) if out and out != "0" else 0
except:
return 0
def main():
if not os.path.exists(OVERLAY_PATH):
print(f"[!] Error: Path not found: {OVERLAY_PATH}")
sys.exit(1)
print("[*] Fetching Podman metadata...")
try:
# Avoid --all flag by passing specific IDs
c_ids = subprocess.check_output(["podman", "ps", "-aq"], text=True).split()
i_ids = subprocess.check_output(["podman", "images", "-aq"], text=True).split()
all_ids = list(set(c_ids + i_ids))
if not all_ids:
print("[!] No containers or images found.")
return
raw_inspect = subprocess.check_output(["podman", "inspect"] + all_ids, text=True)
metadata = json.loads(raw_inspect)
except Exception as e:
print(f"[!] Metadata error: {e}")
return
# Get physical folders (hex IDs are 64 chars)
try:
physical_folders = [f for f in os.listdir(OVERLAY_PATH) if os.path.isdir(os.path.join(OVERLAY_PATH, f)) and len(f) > 60]
except PermissionError:
print("[!] Permission Denied."); return
total_folders = len(physical_folders)
results = []
print(f"Analyzing {total_folders} folders...")
for i, folder in enumerate(physical_folders):
full_path = os.path.join(OVERLAY_PATH, folder)
found_images = []
found_containers = []
# Verified matching logic from singleton
for item in metadata:
item_type = "Container" if item.get("State") else "Image"
item_name = item.get("Name", "").lstrip("/")
if not item_name and "RepoTags" in item:
item_name = ", ".join(item.get("RepoTags", ["<none>"]))
gd = item.get("GraphDriver", {}).get("Data", {})
match = False
for key in ["UpperDir", "LowerDir", "MergedDir", "WorkDir"]:
val = gd.get(key)
if val and folder in val:
match = True
break
if match:
if item_type == "Container":
found_containers.append(item_name)
else:
found_images.append(item_name)
results.append({
'folder': folder,
'size': get_folder_size(full_path),
'image': ", ".join(list(set(found_images))),
'container': ", ".join(list(set(found_containers)))
})
# Progress bar
progress = (i + 1) / total_folders
bar_len = 40
filled = int(bar_len * progress)
bar = '█' * filled + '-' * (bar_len - filled)
sys.stdout.write(f"\r|{bar}| {int(progress * 100)}% Complete")
sys.stdout.flush()
# Final sort: Smallest to Largest
results.sort(key=lambda x: x['size'])
print("\n" + "_" * 75)
for res in results:
# Show folders with associations or size > 10MB
if res['image'] or res['container'] or res['size'] > 10485760:
print(f"\nFolder: | {res['folder']} ({format_size(res['size'])})")
print(f"Image: | {res['image']}")
print(f"Container: | {res['container']}")
print("_" * 75)
if __name__ == "__main__":
main()
# END OF snoop_overlays
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment