Back to skills
SkillHub ClubRun DevOpsFull StackDevOps

docker-container-cleaner

CLI tool to clean up stopped Docker containers, unused images, volumes, and networks to free up disk space.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,077
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
C62.8

Install command

npx @skill-hub/cli install openclaw-skills-docker-container-cleaner

Repository

openclaw/skills

Skill path: skills/derick001/docker-container-cleaner

CLI tool to clean up stopped Docker containers, unused images, volumes, and networks to free up disk space.

Open repository

Best for

Primary workflow: Run DevOps.

Technical facets: Full Stack, DevOps.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install docker-container-cleaner into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding docker-container-cleaner to shared team environments
  • Use docker-container-cleaner for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: docker-container-cleaner
description: CLI tool to clean up stopped Docker containers, unused images, volumes, and networks to free up disk space.
version: 1.0.0
author: skill-factory
metadata:
  openclaw:
    requires:
      bins:
        - python3
        - docker
---

# Docker Container Cleaner

## What This Does

A CLI tool that helps clean up Docker resources to free up disk space. It can:
- List and remove stopped containers
- Remove dangling images (images with no tag)
- Remove unused images (not used by any container)
- Remove unused volumes
- Remove unused networks
- Perform a "prune all" operation (Docker system prune)

The tool provides a safe, interactive mode by default, showing what will be removed and asking for confirmation before deleting anything.

## When To Use

- Your Docker disk usage is growing and you need to free up space
- You have many stopped containers that are no longer needed
- You have old, unused images taking up disk space
- You want to clean up Docker resources in a controlled, safe way
- You need to automate Docker cleanup in scripts or CI/CD pipelines

## Usage

Interactive cleanup (recommended for first use):
```bash
python3 scripts/main.py clean
```

Remove stopped containers only:
```bash
python3 scripts/main.py clean --containers
```

Remove dangling images only:
```bash
python3 scripts/main.py clean --images --dangling
```

Remove unused images (all images not used by containers):
```bash
python3 scripts/main.py clean --images --unused
```

Remove unused volumes:
```bash
python3 scripts/main.py clean --volumes
```

Remove unused networks:
```bash
python3 scripts/main.py clean --networks
```

Force cleanup (no confirmation):
```bash
python3 scripts/main.py clean --all --force
```

Dry run (show what would be removed):
```bash
python3 scripts/main.py clean --all --dry-run
```

## Examples

### Example 1: Interactive cleanup

```bash
python3 scripts/main.py clean
```

Output:
```
Docker Cleanup Tool
===================

Found resources:
- Stopped containers: 3 (using 1.2GB)
- Dangling images: 5 (using 850MB)
- Unused images: 2 (using 450MB)
- Unused volumes: 1 (using 100MB)
- Unused networks: 0

Total disk space that can be freed: 2.6GB

What would you like to clean up?
1. Remove stopped containers
2. Remove dangling images
3. Remove unused images
4. Remove unused volumes
5. Remove unused networks
6. All of the above
7. Cancel

Enter choice [1-7]: 2

About to remove 5 dangling images (850MB):
- python:3.9-alpine (dangling)
- node:16-slim (dangling)
- ...

Are you sure? (y/N): y
Removing images...
✅ Cleanup complete! Freed 850MB of disk space.
```

### Example 2: Script-friendly JSON output

```bash
python3 scripts/main.py status --format json
```

Output:
```json
{
  "containers": {
    "running": 2,
    "stopped": 3,
    "stopped_size_mb": 1200
  },
  "images": {
    "total": 15,
    "dangling": 5,
    "dangling_size_mb": 850,
    "unused": 2,
    "unused_size_mb": 450
  },
  "volumes": {
    "total": 4,
    "unused": 1,
    "unused_size_mb": 100
  },
  "networks": {
    "total": 3,
    "unused": 0
  },
  "total_reclaimable_mb": 2600
}
```

## Requirements

- Python 3.x
- **Docker**: Must be installed and the Docker daemon must be running
- **Docker CLI**: Must be available in PATH (`docker` command)
- **Docker SDK for Python**: Optional, but recommended for better performance

Install Docker SDK for Python (optional):
```bash
pip install docker
```

## Limitations

- This is a CLI tool, not an auto-integration plugin
- Requires Docker daemon to be running and accessible
- Some operations require elevated permissions (sudo)
- Cannot clean up resources in use by running containers
- Image size calculations are approximate
- Network and volume cleanup may fail if resources are in use
- Does not clean up Docker build cache (use `docker builder prune`)
- Does not clean up Docker Compose resources automatically
- Performance depends on number of Docker resources
- Large cleanup operations may take significant time

---

## Referenced Files

> The following files are referenced in this skill and included for context.

### scripts/main.py

```python
#!/usr/bin/env python3
"""
Docker Container Cleaner - CLI tool to clean up Docker resources
"""

import argparse
import json
import os
import subprocess
import sys
import time
from typing import Dict, List, Optional, Any, Tuple
from datetime import datetime, timezone

def check_docker_available() -> bool:
    """Check if Docker is installed and running."""
    try:
        result = subprocess.run(["docker", "version"], 
                              capture_output=True, text=True, timeout=5)
        return result.returncode == 0
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return False

def run_docker_command(cmd: List[str], timeout: int = 30) -> Dict[str, Any]:
    """Run a docker command and return standardized result."""
    start_time = time.time()
    try:
        result = subprocess.run(
            ["docker"] + cmd,
            capture_output=True,
            text=True,
            timeout=timeout
        )
        elapsed = time.time() - start_time
        
        return {
            "success": result.returncode == 0,
            "stdout": result.stdout,
            "stderr": result.stderr,
            "returncode": result.returncode,
            "elapsed_time": elapsed
        }
    except subprocess.TimeoutExpired:
        return {
            "success": False,
            "error": f"Command timed out after {timeout} seconds",
            "elapsed_time": time.time() - start_time
        }
    except Exception as e:
        return {
            "success": False,
            "error": str(e),
            "elapsed_time": time.time() - start_time
        }

def parse_size(size_str: str) -> int:
    """Parse Docker size string (e.g., '1.2GB', '450MB') to bytes."""
    if not size_str:
        return 0
    
    size_str = size_str.upper().strip()
    
    # Extract numeric part
    import re
    match = re.match(r"([\d.]+)\s*([KMGTP]?B)", size_str)
    if not match:
        return 0
    
    value = float(match.group(1))
    unit = match.group(2)
    
    multipliers = {
        "B": 1,
        "KB": 1024,
        "MB": 1024 ** 2,
        "GB": 1024 ** 3,
        "TB": 1024 ** 4,
        "PB": 1024 ** 5
    }
    
    return int(value * multipliers.get(unit, 1))

def format_size(bytes: int) -> str:
    """Format bytes to human readable string."""
    if bytes == 0:
        return "0B"
    
    units = ["B", "KB", "MB", "GB", "TB"]
    unit_index = 0
    
    while bytes >= 1024 and unit_index < len(units) - 1:
        bytes /= 1024
        unit_index += 1
    
    return f"{bytes:.1f}{units[unit_index]}"

def get_containers_info() -> Dict[str, Any]:
    """Get information about Docker containers."""
    # Get running containers
    running_cmd = run_docker_command(["ps", "--format", "{{.ID}} {{.Names}}", "--no-trunc"])
    running = []
    if running_cmd["success"]:
        for line in running_cmd["stdout"].strip().split("\n"):
            if line:
                parts = line.split(" ", 1)
                if len(parts) == 2:
                    running.append({"id": parts[0], "name": parts[1]})
    
    # Get stopped containers
    stopped_cmd = run_docker_command(["ps", "-a", "--filter", "status=exited", "--format", "{{.ID}} {{.Names}} {{.Size}}", "--no-trunc"])
    stopped = []
    total_stopped_size = 0
    if stopped_cmd["success"]:
        for line in stopped_cmd["stdout"].strip().split("\n"):
            if line:
                parts = line.split(" ", 2)
                if len(parts) >= 2:
                    container_info = {"id": parts[0], "name": parts[1]}
                    if len(parts) == 3:
                        size_str = parts[2]
                        container_info["size"] = size_str
                        total_stopped_size += parse_size(size_str)
                    stopped.append(container_info)
    
    return {
        "running": running,
        "stopped": stopped,
        "stopped_count": len(stopped),
        "stopped_size_bytes": total_stopped_size,
        "running_count": len(running)
    }

def get_images_info() -> Dict[str, Any]:
    """Get information about Docker images."""
    # Get all images
    images_cmd = run_docker_command(["images", "--format", "{{.Repository}}:{{.Tag}} {{.ID}} {{.Size}}", "--no-trunc"])
    all_images = []
    total_size = 0
    
    if images_cmd["success"]:
        for line in images_cmd["stdout"].strip().split("\n"):
            if line:
                parts = line.split(" ", 2)
                if len(parts) == 3:
                    image_info = {
                        "repository": parts[0],
                        "id": parts[1],
                        "size": parts[2]
                    }
                    all_images.append(image_info)
                    total_size += parse_size(parts[2])
    
    # Get dangling images (images with no tag)
    dangling_cmd = run_docker_command(["images", "-f", "dangling=true", "--format", "{{.ID}} {{.Size}}", "--no-trunc"])
    dangling_images = []
    dangling_size = 0
    
    if dangling_cmd["success"]:
        for line in dangling_cmd["stdout"].strip().split("\n"):
            if line:
                parts = line.split(" ", 1)
                if len(parts) == 2:
                    dangling_images.append({"id": parts[0], "size": parts[1]})
                    dangling_size += parse_size(parts[1])
    
    return {
        "all": all_images,
        "total_count": len(all_images),
        "total_size_bytes": total_size,
        "dangling": dangling_images,
        "dangling_count": len(dangling_images),
        "dangling_size_bytes": dangling_size
    }

def get_volumes_info() -> Dict[str, Any]:
    """Get information about Docker volumes."""
    # Get all volumes
    volumes_cmd = run_docker_command(["volume", "ls", "--format", "{{.Name}} {{.Driver}}", "--no-trunc"])
    all_volumes = []
    
    if volumes_cmd["success"]:
        for line in volumes_cmd["stdout"].strip().split("\n"):
            if line:
                parts = line.split(" ", 1)
                if len(parts) == 2:
                    all_volumes.append({"name": parts[0], "driver": parts[1]})
    
    # Check for unused volumes (simplified - in real implementation would check mount points)
    # For now, we'll assume a volume is unused if it's not mounted by a running container
    # This is a simplified check
    unused_volumes = []
    unused_count = len(all_volumes)  # Simplified - in reality would need to check mount points
    
    return {
        "all": all_volumes,
        "total_count": len(all_volumes),
        "unused": unused_volumes,
        "unused_count": unused_count
    }

def get_networks_info() -> Dict[str, Any]:
    """Get information about Docker networks."""
    # Get all networks
    networks_cmd = run_docker_command(["network", "ls", "--format", "{{.Name}} {{.Driver}}", "--no-trunc"])
    all_networks = []
    
    if networks_cmd["success"]:
        for line in networks_cmd["stdout"].strip().split("\n"):
            if line:
                parts = line.split(" ", 1)
                if len(parts) == 2:
                    all_networks.append({"name": parts[0], "driver": parts[1]})
    
    # Check for unused networks (simplified)
    unused_networks = []
    unused_count = len(all_networks)  # Simplified - in reality would need to check if used by containers
    
    return {
        "all": all_networks,
        "total_count": len(all_networks),
        "unused": unused_networks,
        "unused_count": unused_count
    }

def get_resource_summary() -> Dict[str, Any]:
    """Get summary of all Docker resources."""
    containers = get_containers_info()
    images = get_images_info()
    volumes = get_volumes_info()
    networks = get_networks_info()
    
    total_reclaimable = (
        containers["stopped_size_bytes"] +
        images["dangling_size_bytes"]
    )
    
    return {
        "containers": containers,
        "images": images,
        "volumes": volumes,
        "networks": networks,
        "total_reclaimable_bytes": total_reclaimable,
        "timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
    }

def clean_containers(force: bool = False, dry_run: bool = False) -> Dict[str, Any]:
    """Clean up stopped containers."""
    if dry_run:
        containers = get_containers_info()
        return {
            "operation": "clean_containers",
            "dry_run": True,
            "would_remove": containers["stopped_count"],
            "would_reclaim_bytes": containers["stopped_size_bytes"]
        }
    
    cmd = ["container", "prune"]
    if force:
        cmd.append("--force")
    else:
        cmd.append("--force")  # Always force for non-interactive mode
    
    result = run_docker_command(cmd)
    
    if result["success"]:
        # Parse output to get reclaimed space
        reclaimed = 0
        if "Total reclaimed space:" in result["stdout"]:
            import re
            match = re.search(r"Total reclaimed space:\s*([\d.]+\s*[KMGTP]?B)", result["stdout"])
            if match:
                reclaimed = parse_size(match.group(1))
        
        return {
            "operation": "clean_containers",
            "success": True,
            "reclaimed_bytes": reclaimed,
            "output": result["stdout"]
        }
    else:
        return {
            "operation": "clean_containers",
            "success": False,
            "error": result.get("error", result.get("stderr", "Unknown error"))
        }

def clean_images(dangling: bool = False, unused: bool = False, force: bool = False, dry_run: bool = False) -> Dict[str, Any]:
    """Clean up Docker images."""
    if not dangling and not unused:
        return {
            "operation": "clean_images",
            "success": False,
            "error": "Must specify --dangling or --unused"
        }
    
    if dry_run:
        images = get_images_info()
        would_remove = 0
        would_reclaim = 0
        
        if dangling:
            would_remove += images["dangling_count"]
            would_reclaim += images["dangling_size_bytes"]
        
        # Note: unused images check would need more sophisticated logic
        # For now, we'll just report dangling
        
        return {
            "operation": "clean_images",
            "dry_run": True,
            "would_remove": would_remove,
            "would_reclaim_bytes": would_reclaim,
            "dangling": dangling,
            "unused": unused
        }
    
    cmd = ["image", "prune"]
    
    if dangling and unused:
        # Prune all images
        cmd.append("--all")
    elif dangling:
        # Only dangling
        pass  # Default is dangling
    elif unused:
        # This would require different logic - for now use --all
        cmd.append("--all")
    
    if force:
        cmd.append("--force")
    else:
        cmd.append("--force")  # Always force for non-interactive mode
    
    result = run_docker_command(cmd)
    
    if result["success"]:
        reclaimed = 0
        if "Total reclaimed space:" in result["stdout"]:
            import re
            match = re.search(r"Total reclaimed space:\s*([\d.]+\s*[KMGTP]?B)", result["stdout"])
            if match:
                reclaimed = parse_size(match.group(1))
        
        return {
            "operation": "clean_images",
            "success": True,
            "reclaimed_bytes": reclaimed,
            "dangling": dangling,
            "unused": unused,
            "output": result["stdout"]
        }
    else:
        return {
            "operation": "clean_images",
            "success": False,
            "error": result.get("error", result.get("stderr", "Unknown error"))
        }

def clean_volumes(force: bool = False, dry_run: bool = False) -> Dict[str, Any]:
    """Clean up unused volumes."""
    if dry_run:
        volumes = get_volumes_info()
        return {
            "operation": "clean_volumes",
            "dry_run": True,
            "would_remove": volumes["unused_count"]
        }
    
    cmd = ["volume", "prune"]
    if force:
        cmd.append("--force")
    else:
        cmd.append("--force")  # Always force for non-interactive mode
    
    result = run_docker_command(cmd)
    
    if result["success"]:
        reclaimed = 0
        if "Total reclaimed space:" in result["stdout"]:
            import re
            match = re.search(r"Total reclaimed space:\s*([\d.]+\s*[KMGTP]?B)", result["stdout"])
            if match:
                reclaimed = parse_size(match.group(1))
        
        return {
            "operation": "clean_volumes",
            "success": True,
            "reclaimed_bytes": reclaimed,
            "output": result["stdout"]
        }
    else:
        return {
            "operation": "clean_volumes",
            "success": False,
            "error": result.get("error", result.get("stderr", "Unknown error"))
        }

def clean_networks(force: bool = False, dry_run: bool = False) -> Dict[str, Any]:
    """Clean up unused networks."""
    if dry_run:
        networks = get_networks_info()
        return {
            "operation": "clean_networks",
            "dry_run": True,
            "would_remove": networks["unused_count"]
        }
    
    cmd = ["network", "prune"]
    if force:
        cmd.append("--force")
    else:
        cmd.append("--force")  # Always force for non-interactive mode
    
    result = run_docker_command(cmd)
    
    if result["success"]:
        return {
            "operation": "clean_networks",
            "success": True,
            "output": result["stdout"]
        }
    else:
        return {
            "operation": "clean_networks",
            "success": False,
            "error": result.get("error", result.get("stderr", "Unknown error"))
        }

def format_table(data: Dict[str, Any]) -> str:
    """Format resource summary as a table."""
    lines = []
    lines.append("Docker Resource Summary")
    lines.append("=" * 50)
    
    # Containers
    containers = data["containers"]
    lines.append(f"Containers:")
    lines.append(f"  Running: {containers['running_count']}")
    lines.append(f"  Stopped: {containers['stopped_count']} ({format_size(containers['stopped_size_bytes'])})")
    
    # Images
    images = data["images"]
    lines.append(f"Images:")
    lines.append(f"  Total: {images['total_count']} ({format_size(images['total_size_bytes'])})")
    lines.append(f"  Dangling: {images['dangling_count']} ({format_size(images['dangling_size_bytes'])})")
    
    # Volumes
    volumes = data["volumes"]
    lines.append(f"Volumes:")
    lines.append(f"  Total: {volumes['total_count']}")
    lines.append(f"  Unused: {volumes['unused_count']}")
    
    # Networks
    networks = data["networks"]
    lines.append(f"Networks:")
    lines.append(f"  Total: {networks['total_count']}")
    lines.append(f"  Unused: {networks['unused_count']}")
    
    # Total reclaimable
    lines.append(f"Total reclaimable space: {format_size(data['total_reclaimable_bytes'])}")
    
    return "\n".join(lines)

def run_status(args):
    """Execute the status command."""
    if not check_docker_available():
        return {
            "status": "error",
            "error": "Docker is not available. Make sure Docker is installed and running."
        }
    
    summary = get_resource_summary()
    
    if args.format == "json":
        return summary
    else:
        summary["formatted"] = format_table(summary)
        return summary

def run_clean(args):
    """Execute the clean command."""
    if not check_docker_available():
        return {
            "status": "error",
            "error": "Docker is not available. Make sure Docker is installed and running."
        }
    
    results = []
    force = args.force or args.yes or args.no_interactive
    
    # Clean containers
    if args.containers or args.all:
        result = clean_containers(force=force, dry_run=args.dry_run)
        results.append(result)
    
    # Clean images
    if args.images or args.all:
        result = clean_images(
            dangling=args.dangling or args.all,
            unused=args.unused or args.all,
            force=force,
            dry_run=args.dry_run
        )
        results.append(result)
    
    # Clean volumes
    if args.volumes or args.all:
        result = clean_volumes(force=force, dry_run=args.dry_run)
        results.append(result)
    
    # Clean networks
    if args.networks or args.all:
        result = clean_networks(force=force, dry_run=args.dry_run)
        results.append(result)
    
    # If nothing was specified but clean command was called, show status
    if not any([args.containers, args.images, args.volumes, args.networks, args.all]):
        return run_status(args)
    
    # Prepare output
    total_reclaimed = 0
    any_failed = False
    errors = []
    
    for result in results:
        if not result.get("success", True) and "dry_run" not in result:
            any_failed = True
            if "error" in result:
                errors.append(f"{result.get('operation', 'Unknown')}: {result['error']}")
        if "reclaimed_bytes" in result:
            total_reclaimed += result["reclaimed_bytes"]
    
    output = {
        "status": "error" if any_failed else "success",
        "operations": results,
        "total_reclaimed_bytes": total_reclaimed,
        "timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
    }
    
    if errors:
        output["errors"] = errors
    
    if args.dry_run:
        output["dry_run"] = True
    
    return output

def main():
    parser = argparse.ArgumentParser(
        description="Clean up Docker resources (containers, images, volumes, networks)."
    )
    parser.add_argument(
        "command",
        choices=["status", "clean", "help"],
        help="Command to execute"
    )
    
    # Common arguments
    parser.add_argument(
        "--format",
        choices=["table", "json"],
        default="table",
        help="Output format (default: table)"
    )
    parser.add_argument(
        "--timeout",
        type=int,
        default=30,
        help="Timeout in seconds for Docker operations (default: 30)"
    )
    
    # Clean command arguments
    parser.add_argument(
        "--containers",
        action="store_true",
        help="Remove stopped containers"
    )
    parser.add_argument(
        "--images",
        action="store_true",
        help="Remove images (requires --dangling or --unused)"
    )
    parser.add_argument(
        "--dangling",
        action="store_true",
        help="Remove dangling images (images with no tag)"
    )
    parser.add_argument(
        "--unused",
        action="store_true",
        help="Remove unused images (not used by any container)"
    )
    parser.add_argument(
        "--volumes",
        action="store_true",
        help="Remove unused volumes"
    )
    parser.add_argument(
        "--networks",
        action="store_true",
        help="Remove unused networks"
    )
    parser.add_argument(
        "--all",
        action="store_true",
        help="Clean all resource types"
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Skip confirmation prompts"
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Show what would be removed without actually removing"
    )
    parser.add_argument(
        "--yes",
        action="store_true",
        help="Auto-answer 'yes' to all prompts"
    )
    parser.add_argument(
        "--no-interactive",
        action="store_true",
        help="Disable interactive mode"
    )
    
    args = parser.parse_args()
    
    if args.command == "status":
        result = run_status(args)
    elif args.command == "clean":
        result = run_clean(args)
    else:
        parser.print_help()
        return
    
    if args.format == "json":
        print(json.dumps(result, indent=2))
    else:
        if "formatted" in result:
            print(result["formatted"])
        elif "status" in result and result["status"] == "error":
            print(f"❌ Error: {result.get('error', 'Unknown error')}")
        elif args.command == "clean" and "operations" in result:
            print("Cleanup Results:")
            print("=" * 50)
            for op in result["operations"]:
                if "dry_run" in op:
                    print(f"{op.get('operation', 'Unknown')}: Would remove {op.get('would_remove', 0)} items")
                    if "would_reclaim_bytes" in op and op["would_reclaim_bytes"] > 0:
                        print(f"  Would reclaim: {format_size(op['would_reclaim_bytes'])}")
                elif op.get("success", False):
                    print(f"✅ {op.get('operation', 'Unknown')}: Success")
                    if "reclaimed_bytes" in op and op["reclaimed_bytes"] > 0:
                        print(f"  Reclaimed: {format_size(op['reclaimed_bytes'])}")
                else:
                    print(f"❌ {op.get('operation', 'Unknown')}: {op.get('error', 'Unknown error')}")
            
            if "total_reclaimed_bytes" in result and result["total_reclaimed_bytes"] > 0:
                print(f"\\nTotal reclaimed: {format_size(result['total_reclaimed_bytes'])}")
        else:
            print(json.dumps(result, indent=2))

if __name__ == "__main__":
    main()
```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### README.md

```markdown
# Docker Container Cleaner

A command-line tool to clean up Docker resources (containers, images, volumes, networks) to free up disk space.

## Installation

This skill requires:
- Python 3.x
- **Docker**: Must be installed and the Docker daemon must be running
- **Docker CLI**: Must be available in PATH (`docker` command)

Optional but recommended:
```bash
pip install docker
```

The Docker SDK for Python provides better performance and more reliable operations than shelling out to the `docker` CLI.

## Quick Start

```bash
# Interactive cleanup (recommended for first use)
python3 scripts/main.py clean

# Show current Docker resource usage
python3 scripts/main.py status

# Remove stopped containers only
python3 scripts/main.py clean --containers

# Remove dangling images only
python3 scripts/main.py clean --images --dangling

# Force cleanup of everything (no confirmation)
python3 scripts/main.py clean --all --force

# Dry run (show what would be removed)
python3 scripts/main.py clean --all --dry-run
```

## Command Reference

### `status` command
Show current Docker resource usage.

```
python3 scripts/main.py status [OPTIONS]
```

Options:
- `--format`: Output format: `table` (human-readable) or `json`. Default: `table`
- `--verbose`: Show detailed information

### `clean` command
Clean up Docker resources.

```
python3 scripts/main.py clean [OPTIONS]
```

Options:
- `--containers`: Remove stopped containers
- `--images`: Remove images (requires --dangling or --unused)
- `--dangling`: Remove dangling images (images with no tag)
- `--unused`: Remove unused images (not used by any container)
- `--volumes`: Remove unused volumes
- `--networks`: Remove unused networks
- `--all`: Clean all resource types (equivalent to --containers --images --volumes --networks)
- `--force`: Skip confirmation prompts
- `--dry-run`: Show what would be removed without actually removing
- `--yes`: Auto-answer "yes" to all prompts
- `--no-interactive`: Disable interactive mode (use with --yes for scripts)
- `--timeout`: Timeout in seconds for Docker operations. Default: 30

## Examples

### Basic Usage

```bash
# Check current Docker resource usage
python3 scripts/main.py status

# Interactive cleanup
python3 scripts/main.py clean

# Output:
# Docker Cleanup Tool
# ===================
# 
# Found resources:
# - Stopped containers: 3 (using 1.2GB)
# - Dangling images: 5 (using 850MB)
# - Unused images: 2 (using 450MB)
# - Unused volumes: 1 (using 100MB)
# - Unused networks: 0
# 
# Total disk space that can be freed: 2.6GB
# 
# What would you like to clean up?
# 1. Remove stopped containers
# 2. Remove dangling images
# 3. Remove unused images
# 4. Remove unused volumes
# 5. Remove unused networks
# 6. All of the above
# 7. Cancel
```

### Script Usage

```bash
# Remove stopped containers without prompting
python3 scripts/main.py clean --containers --force

# Remove dangling images and show what was removed
python3 scripts/main.py clean --images --dangling --yes

# JSON output for scripting
python3 scripts/main.py status --format json

# Dry run to see what would be removed
python3 scripts/main.py clean --all --dry-run
```

### Advanced Examples

```bash
# Clean up everything except volumes
python3 scripts/main.py clean --containers --images --networks --force

# Only clean images (both dangling and unused)
python3 scripts/main.py clean --images --dangling --unused --yes

# Clean with a timeout for slow Docker daemons
python3 scripts/main.py clean --all --timeout 60 --yes
```

## How It Works

1. **Resource Detection**: The tool uses the Docker API (via `docker` CLI or Docker SDK) to inspect current resources.

2. **Resource Types**:
   - **Stopped containers**: Containers that are not running
   - **Dangling images**: Images with no tag (usually intermediate build layers)
   - **Unused images**: Images not referenced by any container
   - **Unused volumes**: Volumes not mounted by any container
   - **Unused networks**: Networks not used by any container

3. **Safety Features**:
   - Interactive mode by default (asks for confirmation)
   - Dry run option to preview changes
   - Clear reporting of what will be removed
   - Size estimates for reclaimed space

4. **Cleanup Operations**:
   - Uses `docker container prune` for containers
   - Uses `docker image prune` for images
   - Uses `docker volume prune` for volumes
   - Uses `docker network prune` for networks

## Error Handling

.

If Docker is not running:
```
❌ Docker daemon is not running. Please start Docker and try again.
```

If permission denied:
```
❌ Permission denied. You may need to run with sudo or add your user to the docker group.
```

If timeout occurs:
```
❌ Operation timed out. Docker daemon may be busy. Try increasing timeout with --timeout.
```

If resource is in use:
```
❌ Cannot remove resource [name] because it is in use. Stop using containers first.
```

## Security Considerations

1. **Permission requirements**: Some operations may require `sudo` or membership in the `docker` group.

2. **Data loss**: Removing containers, images, or volumes can result in data loss. Always ensure you have backups.

3. **Production environments**: Use with caution in production. Consider:
   - Running during maintenance windows
   - Using `--dry-run` first
   - Setting up backups before cleanup
   - Testing in staging first

4. **Automation**: When automating in CI/CD:
   - Use `--dry-run` in pipelines
   - Set appropriate timeouts
   - Log all operations
   - Have rollback plans

## Limitations

- **Docker daemon required**: Must have access to running Docker daemon
- **Performance**: Large numbers of resources can slow down detection
- **Size calculations**: Approximate; actual freed space may vary
- **Build cache**: Does not clean Docker build cache (use `docker builder prune`)
- **Swarm mode**: Limited support for Docker Swarm resources
- **Windows containers**: Primarily tested with Linux containers

## Development

The tool is designed to be extensible. To add support for additional resource types:

1. Add detection logic in `detect_resources()`
2. Add cleanup function following the pattern `clean_<resource_type>()`
3. Add command-line argument parsing
4. Update documentation

## License

MIT
```

### _meta.json

```json
{
  "owner": "derick001",
  "slug": "docker-container-cleaner",
  "displayName": "Docker Container Cleaner",
  "latest": {
    "version": "1.0.0",
    "publishedAt": 1772287689576,
    "commit": "https://github.com/openclaw/skills/commit/5acbb9f1dcb0b05b25c8ea669fc6bce1e66e7129"
  },
  "history": []
}

```

docker-container-cleaner | SkillHub