commit bf46819e1adbc7a1485c5774247201426e0d8f68 Author: jpmvaz Date: Sun Sep 13 19:54:18 2026 +0100 v1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ffd099e --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# Docker Factory Reset Script for Ubuntu + +A comprehensive Python script that completely resets Docker to a fresh installation state on Ubuntu systems. This script removes all containers, images, volumes, networks, build cache, and even wipes the Docker data directories to give you a clean slate. + +## 🚨 Warning + +**This script is EXTREMELY DESTRUCTIVE!** + +It will permanently delete: +- āœ— All Docker containers (running and stopped) +- āœ— All Docker images +- āœ— All Docker volumes (including all data stored in them) +- āœ— All custom Docker networks +- āœ— All build cache +- āœ— All Docker system data (`/var/lib/docker` and `/var/lib/containerd`) + +**There is NO undo. All data will be permanently lost.** + +Only use this script if you: +- Want to completely reset Docker to a fresh state +- Are experiencing issues that require a clean Docker installation +- Are decommissioning a system and want to clean up Docker +- Understand that ALL Docker data will be lost + +## šŸ“‹ Features + +- āœ… **Real-time output**: See exactly what's happening as commands execute +- āœ… **Progress indicators**: Visual progress bars for batch operations +- āœ… **Step-by-step execution**: Clear indication of each phase +- āœ… **Safety confirmation**: Requires explicit confirmation before proceeding +- āœ… **Comprehensive cleanup**: Removes everything Docker-related +- āœ… **Service management**: Properly stops and restarts Docker daemon +- āœ… **Final state report**: Shows the clean state after reset +- āœ… **Execution time tracking**: Reports how long the reset took + +## šŸ”§ Requirements + +### System Requirements +- **Operating System**: Ubuntu (18.04, 20.04, 22.04, 24.04 or later) +- **Docker**: Must be installed (any version) +- **Python**: Python 3.6 or later (usually pre-installed on Ubuntu) +- **Privileges**: Must be run as root (using `sudo`) + +### Dependencies +All dependencies are part of Python's standard library: +- `subprocess` - For running system commands +- `sys` - For system operations +- `os` - For file system operations +- `time` - For timing and delays + +No additional packages need to be installed. + +## šŸš€ Usage + +Run the script with sudo: + +```bash +sudo python3 docker_full_factory_reset.py +``` + +The script will ask you to type `RESET_DOCKER_UBUNTU` to confirm. + +## šŸ“– What Each Step Does + +1. **Containers**: Stops and removes all containers +2. **Images**: Removes all images +3. **Networks**: Removes custom networks (keeps default ones) +4. **Volumes**: Removes all volumes +5. **Prune**: Clears build cache and system artifacts +6. **Stop services**: Stops `docker` and `containerd` +7. **Delete directories**: Removes `/var/lib/docker` and `/var/lib/containerd` +8. **Start services**: Restarts Docker and verifies with `docker info` + +## šŸ” Verify After Reset + +```bash +docker run hello-world +``` + +## ⚠ Final Warning + +This script deletes **ALL** Docker data. Back up anything important first. diff --git a/docker_full_factory_reset.py b/docker_full_factory_reset.py new file mode 100644 index 0000000..e717b3b --- /dev/null +++ b/docker_full_factory_reset.py @@ -0,0 +1,406 @@ +#!/usr/bin/env python3 +""" +Docker Factory Reset Script for Ubuntu +Completely resets Docker to a fresh installation state. +""" +import subprocess +import sys +import os +import time +from typing import List + + +class ProgressBar: + """Simple progress indicator for operations.""" + + def __init__(self, total: int, description: str = ""): + self.total = total + self.current = 0 + self.description = description + + def update(self, increment: int = 1): + self.current += increment + self._display() + + def _display(self): + if self.total == 0: + percentage = 100 + else: + percentage = int((self.current / self.total) * 100) + + bar_length = 40 + filled = int((percentage / 100) * bar_length) + bar = 'ā–ˆ' * filled + 'ā–‘' * (bar_length - filled) + + print(f"\r [{bar}] {percentage}% ({self.current}/{self.total}) {self.description}", end='', flush=True) + + def complete(self): + self.current = self.total + self._display() + print() # New line after completion + + +def run_cmd_realtime(cmd: List[str], check: bool = False, show_output: bool = True) -> int: + """ + Run a command and stream stdout/stderr in real-time. + Returns the exit code. + If check=True, exits the script on non-zero return code. + """ + if show_output: + print(f"\nā–¶ RUN: {' '.join(cmd)}") + print("─" * 60) + + try: + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, # Merge stderr into stdout + text=True, + bufsize=1, # Line buffered + ) + + # Stream output line by line + if show_output: + for line in process.stdout: + print(line, end='') # Print without adding extra newline + else: + # Consume output even if not showing + for _line in process.stdout: + pass + + process.wait() + code = process.returncode + + if show_output: + if code != 0: + print(f"āœ— Command exited with code {code}") + if check: + sys.exit(code) + else: + print(f"āœ“ Command completed successfully") + + return code + + except FileNotFoundError: + print(f"āœ— ERROR: Command not found: {' '.join(cmd)}") + sys.exit(1) + + +def run_cmd_quiet(cmd: List[str]) -> tuple: + """ + Run a command quietly and return (exit_code, stdout, stderr). + Used for listing/checking operations. + """ + try: + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + return result.returncode, result.stdout.strip(), result.stderr.strip() + except FileNotFoundError: + print(f"āœ— ERROR: Command not found: {' '.join(cmd)}") + sys.exit(1) + + +def require_root(): + if os.geteuid() != 0: + print("āœ— This script must be run as root (use sudo).") + sys.exit(1) + + +def docker_installed() -> bool: + code, out, err = run_cmd_quiet(["docker", "--version"]) + if code != 0: + print("āœ— Docker does not appear to be installed or accessible.") + if err: + print(err) + return False + print(f"āœ“ Docker found: {out}") + return True + + +def list_ids(cmd: List[str]) -> List[str]: + code, out, _err = run_cmd_quiet(cmd) + if code != 0: + return [] + return [line.strip() for line in out.splitlines() if line.strip()] + + +def stop_and_remove_containers(): + print("\n" + "=" * 60) + print("STEP 1/8: CONTAINERS") + print("=" * 60) + + container_ids = list_ids(["docker", "ps", "-aq"]) + if not container_ids: + print("ℹ No containers found.") + return + + print(f"ℹ Found {len(container_ids)} container(s)") + + running_ids = list_ids(["docker", "ps", "-q"]) + if running_ids: + print(f"\nℹ Stopping {len(running_ids)} running container(s)...") + progress = ProgressBar(len(running_ids), "stopping containers") + + for container_id in running_ids: + run_cmd_realtime(["docker", "stop", container_id], show_output=False) + progress.update() + progress.complete() + else: + print("ℹ No running containers to stop.") + + print(f"\nℹ Removing all {len(container_ids)} container(s)...") + progress = ProgressBar(len(container_ids), "removing containers") + + for container_id in container_ids: + run_cmd_realtime(["docker", "rm", "-f", container_id], show_output=False) + progress.update() + progress.complete() + + print("āœ“ All containers removed") + + +def remove_images(): + print("\n" + "=" * 60) + print("STEP 2/8: IMAGES") + print("=" * 60) + + image_ids = list_ids(["docker", "images", "-aq"]) + if not image_ids: + print("ℹ No images found.") + return + + print(f"ℹ Found {len(image_ids)} image(s)") + print("\nℹ Removing all images...") + progress = ProgressBar(len(image_ids), "removing images") + + for image_id in image_ids: + run_cmd_realtime(["docker", "rmi", "-f", image_id], show_output=False) + progress.update() + progress.complete() + + print("āœ“ All images removed") + + +def remove_networks(): + print("\n" + "=" * 60) + print("STEP 3/8: NETWORKS") + print("=" * 60) + + code, out, err = run_cmd_quiet(["docker", "network", "ls", "--format", "{{.Name}}"]) + if code != 0: + print(f"āœ— Error listing networks: {err}") + return + + default_networks = {"bridge", "host", "none"} + networks = [n.strip() for n in out.splitlines() if n.strip()] + custom_networks = [n for n in networks if n not in default_networks] + + if not custom_networks: + print("ℹ No custom networks found.") + return + + print(f"ℹ Found {len(custom_networks)} custom network(s): {', '.join(custom_networks)}") + print("\nℹ Removing custom networks...") + progress = ProgressBar(len(custom_networks), "removing networks") + + for network in custom_networks: + run_cmd_realtime(["docker", "network", "rm", network], show_output=False) + progress.update() + progress.complete() + + print("āœ“ All custom networks removed") + + +def remove_volumes(): + print("\n" + "=" * 60) + print("STEP 4/8: VOLUMES") + print("=" * 60) + + volume_names = list_ids(["docker", "volume", "ls", "-q"]) + if not volume_names: + print("ℹ No volumes found.") + return + + print(f"ℹ Found {len(volume_names)} volume(s)") + print("\nℹ Removing all volumes...") + progress = ProgressBar(len(volume_names), "removing volumes") + + for volume in volume_names: + run_cmd_realtime(["docker", "volume", "rm", "-f", volume], show_output=False) + progress.update() + progress.complete() + + print("āœ“ All volumes removed") + + +def prune_system(): + print("\n" + "=" * 60) + print("STEP 5/8: SYSTEM PRUNE & BUILD CACHE") + print("=" * 60) + + print("ℹ Pruning builder cache...") + run_cmd_realtime(["docker", "builder", "prune", "-af"]) + + print("\nℹ Running system prune (extra safety)...") + run_cmd_realtime(["docker", "system", "prune", "-af", "--volumes"]) + + +def stop_docker_service(): + print("\n" + "=" * 60) + print("STEP 6/8: STOP DOCKER DAEMON") + print("=" * 60) + + print("ℹ Stopping Docker service...") + run_cmd_realtime(["systemctl", "stop", "docker"]) + + print("\nℹ Stopping containerd service...") + run_cmd_realtime(["systemctl", "stop", "containerd"]) + + print("āœ“ Docker daemon stopped") + + +def wipe_docker_data_dirs(): + print("\n" + "=" * 60) + print("STEP 7/8: DELETE DOCKER DATA DIRECTORIES") + print("=" * 60) + + paths = ["/var/lib/docker", "/var/lib/containerd"] + existing_paths = [p for p in paths if os.path.exists(p)] + + if not existing_paths: + print("ℹ No Docker data directories found.") + return + + print(f"ℹ Found {len(existing_paths)} data director(ies) to delete") + progress = ProgressBar(len(existing_paths), "deleting directories") + + for p in paths: + if os.path.exists(p): + print(f"\nℹ Deleting {p} ...") + code, out, _err = run_cmd_quiet(["du", "-sh", p]) + if code == 0 and out: + print(f" Size: {out.split()[0]}") + + run_cmd_realtime(["rm", "-rf", p], show_output=False) + progress.update() + else: + print(f"ℹ {p} does not exist; skipping.") + + progress.complete() + print("āœ“ All Docker data directories deleted") + + +def start_docker_service(): + print("\n" + "=" * 60) + print("STEP 8/8: START DOCKER DAEMON") + print("=" * 60) + + print("ℹ Starting containerd service...") + run_cmd_realtime(["systemctl", "start", "containerd"]) + + print("\nℹ Starting Docker service...") + run_cmd_realtime(["systemctl", "start", "docker"]) + + print("\nℹ Waiting for Docker to initialize...") + for i in range(5): + time.sleep(1) + code, out, _err = run_cmd_quiet(["systemctl", "is-active", "docker"]) + if code == 0 and out.strip() == "active": + print(f"āœ“ Docker service is active (took {i+1}s)") + break + else: + print("⚠ Warning: Docker service may not be active yet.") + + print("\nℹ Running docker info to verify...") + run_cmd_realtime(["docker", "info"]) + + +def show_final_state(): + print("\n" + "=" * 60) + print("FINAL DOCKER STATE") + print("=" * 60) + + cmds = [ + (["docker", "ps", "-a"], "CONTAINERS"), + (["docker", "images"], "IMAGES"), + (["docker", "network", "ls"], "NETWORKS"), + (["docker", "volume", "ls"], "VOLUMES"), + ] + + for cmd, title in cmds: + print(f"\nā–¶ {title}") + print("─" * 60) + code, out, err = run_cmd_quiet(cmd) + if code == 0: + print(out if out else "(empty)") + else: + print("āœ— Command failed") + if err: + print(err) + + +def show_banner(): + banner = """ +╔════════════════════════════════════════════════════════════╗ +ā•‘ ā•‘ +ā•‘ DOCKER FACTORY RESET FOR UBUNTU ā•‘ +ā•‘ ā•‘ +ā•‘ Completely resets Docker to fresh state ā•‘ +ā•‘ ā•‘ +ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• +""" + print(banner) + + +def main(): + show_banner() + require_root() + + if not docker_installed(): + sys.exit(1) + + print("\n⚠ WARNING: This script will COMPLETELY RESET Docker on this Ubuntu host.") + print("\nIt will:") + print(" • Stop and remove ALL containers (running and stopped)") + print(" • Remove ALL images") + print(" • Remove ALL volumes") + print(" • Remove ALL custom networks") + print(" • Prune ALL build cache and system data") + print(" • STOP Docker daemon") + print(" • DELETE /var/lib/docker and /var/lib/containerd") + print(" • START Docker daemon again") + print("\n⚠ You will lose ALL Docker data on this machine.") + print("\n" + "─" * 60) + + confirm = input("\nType 'RESET_DOCKER_UBUNTU' to continue: ") + if confirm != "RESET_DOCKER_UBUNTU": + print("\nāœ— Aborted.") + sys.exit(0) + + start_time = time.time() + + stop_and_remove_containers() + remove_images() + remove_networks() + remove_volumes() + prune_system() + stop_docker_service() + wipe_docker_data_dirs() + start_docker_service() + show_final_state() + + elapsed_time = time.time() - start_time + + print("\n" + "=" * 60) + print("āœ“ DOCKER FACTORY RESET COMPLETE") + print(f" Total time: {elapsed_time:.2f} seconds") + print("=" * 60) + print("\nDocker has been reset to a fresh installation state.\n") + + +if __name__ == "__main__": + main()