--- # Docker Cleanup and Pruning Playbook # Clean up unused containers, images, volumes, and networks # Usage: ansible-playbook playbooks/prune_containers.yml # Usage: ansible-playbook playbooks/prune_containers.yml -e "aggressive_cleanup=true" # Usage: ansible-playbook playbooks/prune_containers.yml -e "dry_run=true" - name: Docker System Cleanup and Pruning hosts: "{{ host_target | default('all') }}" gather_facts: yes vars: dry_run: "{{ dry_run | default(false) }}" aggressive_cleanup: "{{ aggressive_cleanup | default(false) }}" keep_images_days: "{{ keep_images_days | default(7) }}" keep_volumes: "{{ keep_volumes | default(true) }}" backup_before_cleanup: "{{ backup_before_cleanup | default(true) }}" cleanup_logs: "{{ cleanup_logs | default(true) }}" max_log_size: "{{ max_log_size | default('100m') }}" tasks: - name: Check if Docker is running systemd: name: docker register: docker_status failed_when: docker_status.status.ActiveState != "active" - name: Create cleanup report directory file: path: "/tmp/docker_cleanup/{{ ansible_date_time.date }}" state: directory mode: '0755' - name: Get pre-cleanup Docker system info shell: | echo "=== PRE-CLEANUP DOCKER SYSTEM INFO ===" echo "Date: {{ ansible_date_time.iso8601 }}" echo "Host: {{ inventory_hostname }}" echo "" echo "System Usage:" docker system df echo "" echo "Container Count:" echo "Running: $(docker ps -q | wc -l)" echo "Stopped: $(docker ps -aq --filter status=exited | wc -l)" echo "Total: $(docker ps -aq | wc -l)" echo "" echo "Image Count:" echo "Total: $(docker images -q | wc -l)" echo "Dangling: $(docker images -f dangling=true -q | wc -l)" echo "" echo "Volume Count:" echo "Total: $(docker volume ls -q | wc -l)" echo "Dangling: $(docker volume ls -f dangling=true -q | wc -l)" echo "" echo "Network Count:" echo "Total: $(docker network ls -q | wc -l)" echo "Custom: $(docker network ls --filter type=custom -q | wc -l)" register: pre_cleanup_info changed_when: false - name: Display cleanup plan debug: msg: | ๐Ÿงน DOCKER CLEANUP PLAN ====================== ๐Ÿ–ฅ๏ธ Host: {{ inventory_hostname }} ๐Ÿ“… Date: {{ ansible_date_time.date }} ๐Ÿ” Dry Run: {{ dry_run }} ๐Ÿ’ช Aggressive: {{ aggressive_cleanup }} ๐Ÿ“ฆ Keep Images: {{ keep_images_days }} days ๐Ÿ’พ Keep Volumes: {{ keep_volumes }} ๐Ÿ“ Cleanup Logs: {{ cleanup_logs }} {{ pre_cleanup_info.stdout }} - name: Backup container list before cleanup shell: | backup_file="/tmp/docker_cleanup/{{ ansible_date_time.date }}/{{ inventory_hostname }}_containers_backup.txt" echo "=== CONTAINER BACKUP - {{ ansible_date_time.iso8601 }} ===" > "$backup_file" echo "Host: {{ inventory_hostname }}" >> "$backup_file" echo "" >> "$backup_file" echo "=== RUNNING CONTAINERS ===" >> "$backup_file" docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" >> "$backup_file" echo "" >> "$backup_file" echo "=== ALL CONTAINERS ===" >> "$backup_file" docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.CreatedAt}}" >> "$backup_file" echo "" >> "$backup_file" echo "=== IMAGES ===" >> "$backup_file" docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" >> "$backup_file" echo "" >> "$backup_file" echo "=== VOLUMES ===" >> "$backup_file" docker volume ls >> "$backup_file" echo "" >> "$backup_file" echo "=== NETWORKS ===" >> "$backup_file" docker network ls >> "$backup_file" when: backup_before_cleanup | bool - name: Remove stopped containers shell: | {% if dry_run %} echo "DRY RUN: Would remove stopped containers:" docker ps -aq --filter status=exited {% else %} echo "Removing stopped containers..." stopped_containers=$(docker ps -aq --filter status=exited) if [ -n "$stopped_containers" ]; then docker rm $stopped_containers echo "โœ… Removed stopped containers" else echo "โ„น๏ธ No stopped containers to remove" fi {% endif %} register: remove_stopped_containers - name: Remove dangling images shell: | {% if dry_run %} echo "DRY RUN: Would remove dangling images:" docker images -f dangling=true -q {% else %} echo "Removing dangling images..." dangling_images=$(docker images -f dangling=true -q) if [ -n "$dangling_images" ]; then docker rmi $dangling_images echo "โœ… Removed dangling images" else echo "โ„น๏ธ No dangling images to remove" fi {% endif %} register: remove_dangling_images - name: Remove unused images (aggressive cleanup) shell: | {% if dry_run %} echo "DRY RUN: Would remove unused images older than {{ keep_images_days }} days:" docker images --filter "until={{ keep_images_days * 24 }}h" -q {% else %} echo "Removing unused images older than {{ keep_images_days }} days..." old_images=$(docker images --filter "until={{ keep_images_days * 24 }}h" -q) if [ -n "$old_images" ]; then # Check if images are not used by any container for image in $old_images; do if ! docker ps -a --format "{{.Image}}" | grep -q "$image"; then docker rmi "$image" 2>/dev/null && echo "Removed image: $image" || echo "Failed to remove image: $image" else echo "Skipping image in use: $image" fi done echo "โœ… Removed old unused images" else echo "โ„น๏ธ No old images to remove" fi {% endif %} register: remove_old_images when: aggressive_cleanup | bool - name: Remove dangling volumes shell: | {% if dry_run %} echo "DRY RUN: Would remove dangling volumes:" docker volume ls -f dangling=true -q {% else %} {% if not keep_volumes %} echo "Removing dangling volumes..." dangling_volumes=$(docker volume ls -f dangling=true -q) if [ -n "$dangling_volumes" ]; then docker volume rm $dangling_volumes echo "โœ… Removed dangling volumes" else echo "โ„น๏ธ No dangling volumes to remove" fi {% else %} echo "โ„น๏ธ Volume cleanup skipped (keep_volumes=true)" {% endif %} {% endif %} register: remove_dangling_volumes - name: Remove unused networks shell: | {% if dry_run %} echo "DRY RUN: Would remove unused networks:" docker network ls --filter type=custom -q {% else %} echo "Removing unused networks..." docker network prune -f echo "โœ… Removed unused networks" {% endif %} register: remove_unused_networks - name: Clean up container logs shell: | {% if dry_run %} echo "DRY RUN: Would clean up container logs larger than {{ max_log_size }}" find /var/lib/docker/containers -name "*-json.log" -size +{{ max_log_size }} 2>/dev/null | wc -l {% else %} {% if cleanup_logs %} echo "Cleaning up large container logs (>{{ max_log_size }})..." log_count=0 total_size_before=0 total_size_after=0 for log_file in $(find /var/lib/docker/containers -name "*-json.log" -size +{{ max_log_size }} 2>/dev/null); do if [ -f "$log_file" ]; then size_before=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null || echo 0) total_size_before=$((total_size_before + size_before)) # Truncate log file to last 1000 lines tail -1000 "$log_file" > "${log_file}.tmp" && mv "${log_file}.tmp" "$log_file" size_after=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null || echo 0) total_size_after=$((total_size_after + size_after)) log_count=$((log_count + 1)) fi done if [ $log_count -gt 0 ]; then saved_bytes=$((total_size_before - total_size_after)) echo "โœ… Cleaned $log_count log files, saved $(echo $saved_bytes | numfmt --to=iec) bytes" else echo "โ„น๏ธ No large log files to clean" fi {% else %} echo "โ„น๏ธ Log cleanup skipped (cleanup_logs=false)" {% endif %} {% endif %} register: cleanup_logs_result when: cleanup_logs | bool - name: Run Docker system prune shell: | {% if dry_run %} echo "DRY RUN: Would run docker system prune" docker system df {% else %} echo "Running Docker system prune..." {% if aggressive_cleanup %} docker system prune -af --volumes {% else %} docker system prune -f {% endif %} echo "โœ… Docker system prune complete" {% endif %} register: system_prune_result - name: Get post-cleanup Docker system info shell: | echo "=== POST-CLEANUP DOCKER SYSTEM INFO ===" echo "Date: {{ ansible_date_time.iso8601 }}" echo "Host: {{ inventory_hostname }}" echo "" echo "System Usage:" docker system df echo "" echo "Container Count:" echo "Running: $(docker ps -q | wc -l)" echo "Stopped: $(docker ps -aq --filter status=exited | wc -l)" echo "Total: $(docker ps -aq | wc -l)" echo "" echo "Image Count:" echo "Total: $(docker images -q | wc -l)" echo "Dangling: $(docker images -f dangling=true -q | wc -l)" echo "" echo "Volume Count:" echo "Total: $(docker volume ls -q | wc -l)" echo "Dangling: $(docker volume ls -f dangling=true -q | wc -l)" echo "" echo "Network Count:" echo "Total: $(docker network ls -q | wc -l)" echo "Custom: $(docker network ls --filter type=custom -q | wc -l)" register: post_cleanup_info changed_when: false - name: Generate cleanup report copy: content: | ๐Ÿงน DOCKER CLEANUP REPORT - {{ inventory_hostname }} =============================================== ๐Ÿ“… Cleanup Date: {{ ansible_date_time.iso8601 }} ๐Ÿ–ฅ๏ธ Host: {{ inventory_hostname }} ๐Ÿ” Dry Run: {{ dry_run }} ๐Ÿ’ช Aggressive Mode: {{ aggressive_cleanup }} ๐Ÿ“ฆ Image Retention: {{ keep_images_days }} days ๐Ÿ’พ Keep Volumes: {{ keep_volumes }} ๐Ÿ“ Log Cleanup: {{ cleanup_logs }} ๐Ÿ“Š BEFORE CLEANUP: {{ pre_cleanup_info.stdout }} ๐Ÿ”ง CLEANUP ACTIONS: ๐Ÿ—‘๏ธ Stopped Containers: {{ remove_stopped_containers.stdout }} ๐Ÿ–ผ๏ธ Dangling Images: {{ remove_dangling_images.stdout }} {% if aggressive_cleanup %} ๐Ÿ“ฆ Old Images: {{ remove_old_images.stdout }} {% endif %} ๐Ÿ’พ Dangling Volumes: {{ remove_dangling_volumes.stdout }} ๐ŸŒ Unused Networks: {{ remove_unused_networks.stdout }} {% if cleanup_logs %} ๐Ÿ“ Container Logs: {{ cleanup_logs_result.stdout }} {% endif %} ๐Ÿงน System Prune: {{ system_prune_result.stdout }} ๐Ÿ“Š AFTER CLEANUP: {{ post_cleanup_info.stdout }} ๐Ÿ’ก RECOMMENDATIONS: - Schedule regular cleanup: cron job for this playbook - Monitor disk usage: ansible-playbook playbooks/disk_usage_report.yml - Consider log rotation: ansible-playbook playbooks/log_rotation.yml {% if not aggressive_cleanup %} - For more space: run with -e "aggressive_cleanup=true" {% endif %} โœ… CLEANUP COMPLETE dest: "/tmp/docker_cleanup/{{ ansible_date_time.date }}/{{ inventory_hostname }}_cleanup_report.txt" - name: Display cleanup summary debug: msg: | โœ… DOCKER CLEANUP COMPLETE - {{ inventory_hostname }} ============================================= ๐Ÿ” Mode: {{ 'DRY RUN' if dry_run else 'LIVE CLEANUP' }} ๐Ÿ’ช Aggressive: {{ aggressive_cleanup }} ๐Ÿ“Š SUMMARY: {{ post_cleanup_info.stdout }} ๐Ÿ“„ Full report: /tmp/docker_cleanup/{{ ansible_date_time.date }}/{{ inventory_hostname }}_cleanup_report.txt ๐Ÿ” Next Steps: {% if dry_run %} - Run without dry_run to perform actual cleanup {% endif %} - Monitor: ansible-playbook playbooks/disk_usage_report.yml - Schedule regular cleanup via cron ============================================= - name: Restart Docker daemon if needed systemd: name: docker state: restarted when: - restart_docker | default(false) | bool - not dry_run | bool register: docker_restart - name: Verify services after cleanup ansible.builtin.command: "docker ps --filter name={{ item }} --format '{{ '{{' }}.Names{{ '}}' }}'" loop: - plex - immich-server - vaultwarden - grafana - prometheus register: service_checks changed_when: false failed_when: false when: - not dry_run | bool - name: Display service verification debug: msg: "{{ service_verification.stdout }}" when: service_verification is defined