Docker image & resource cleanup helper, on a schedule!
A sleek, lightweight web interface to automatically clean up Docker resources on a schedule. Built with Python (Flask) · Docker SDK · APScheduler · Gunicorn
Keep your Docker host tidy with scheduled cleanup of unused images, containers, networks, and volumes.
⚠️ DISCLAIMER: PruneMate uses Docker's nativeprunecommands to delete unused resources. This means it removes containers, images, networks, and volumes that Docker considers "unused" - be careful with volumes as they may contain important data. Ensure you understand what will be pruned before enabling automated schedules. The author is not responsible for any data loss or system issues. Use at your own risk.
- 🕐 Flexible scheduling - Daily, Weekly, or Monthly cleanup runs with optional manual-only mode
- 🔀 Schedule control toggle - Enable/disable automatic scheduling, so PruneMate only runs manually
- 🔍 Prune preview - See exactly what will be deleted before executing manual prune operations
- 🌍 Timezone aware - Configure your local timezone
- 🕒 12/24-hour time format - Choose your preferred time display
- 🐳 Multi-host support - Manage multiple Docker hosts from one interface (requires docker-socket-proxy on remote hosts)
- 🧹 Selective cleanup - Choose what to prune: containers, images, networks, volumes, build cache
- 🏗️ Build cache cleanup - Reclaim significant space by pruning Docker builder cache (often 10GB+)
- 📊 All-Time Statistics - Track cumulative space reclaimed and resources deleted across all runs
- 🏠 Homepage integration - Display statistics in your Homepage dashboard (works with authentication enabled)
- 🎨 Modern UI - Dark theme with smooth animations and responsive design
- 🔒 Secure authentication - Optional login protection with password hashing and Basic Auth support
- 🏗️ Multi-architecture support - Native amd64 and arm64 Docker images (Intel/AMD, Raspberry Pi, Apple Silicon)
- 🔒 Safe & controlled - Manual trigger with preview and detailed logging
- 📈 Detailed reports - See exactly what was cleaned and how much space was reclaimed
The overall look and feel of the PruneMate dashboard
The login page, (when enabled in the docker-compose.yaml environment variables)
Add external Docker hosts via docker-socket-proxy
Set up notifications via Gotify, ntfy.sh, Discord, or Telegram to stay informed about cleanup results.
A brief interface that shows which Docker resources will be pruned during the next cleanup run, either manually triggered or scheduled.
- Docker and Docker Compose installed
- Access to Docker socket (
/var/run/docker.sock)
- Create a
docker-compose.yamlfile:
services:
prunemate:
image: anoniemerd/prunemate:latest # Supports amd64 and arm64
container_name: prunemate
ports:
- "7676:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./logs:/var/log
- ./config:/config
environment:
- PRUNEMATE_TZ=Europe/Amsterdam # Change this to your desired timezone
- PRUNEMATE_TIME_24H=true #false for 12-Hour format (AM/PM)
# Optional: Enable authentication (generate hash with: docker run --rm anoniemerd/prunemate python prunemate.py --gen-hash "password")
# - PRUNEMATE_AUTH_USER=admin
# - PRUNEMATE_AUTH_PASSWORD_HASH=your_base64_encoded_hash_here
restart: unless-stopped- Start PruneMate:
docker-compose up -d- Access the web UI of PruneMate:
Open your browser and navigate to:
http://<your-server-ip>:7676/
Using Docker CLI:
docker run -d \
--name prunemate \
-p 7676:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/logs:/var/log \
-v $(pwd)/config:/config \
-e PRUNEMATE_TZ=Europe/Amsterdam \
-e PRUNEMATE_TIME_24H=true \
--restart unless-stopped \
anoniemerd/prunemate:latestAccess the web UI:
http://<your-server-ip>:7676/
Volume explanations:
/var/run/docker.sock- Required for Docker API access./logs- Stores application logs (rotating, 5MB max per file)./config- Stores configuration and state files
| Variable | Default | Description |
|---|---|---|
PRUNEMATE_TZ |
UTC |
Timezone for scheduling (e.g., Europe/Amsterdam, America/New_York) |
PRUNEMATE_TIME_24H |
true |
Time format: true for 24-hour, false for 12-hour (AM/PM) |
PRUNEMATE_CONFIG |
/config/config.json |
Path to configuration file |
PRUNEMATE_AUTH_USER |
admin |
Username for authentication (optional, only used when auth is enabled) |
PRUNEMATE_AUTH_PASSWORD_HASH |
(none) | Base64-encoded password hash (enables authentication when set) |
PruneMate supports optional password protection for the web interface and API endpoints.
Key features:
- 🔒 Form-based login - Styled login page matching the app's design
- 🔑 Secure hashing - Passwords are hashed using scrypt (industry standard)
- 🌐 API compatibility - Basic Auth fallback for external tools (Homepage, Dashy, etc.)
- 🚪 Logout button - Convenient session management
To enable authentication:
- Generate a password hash using the built-in tool:
docker run --rm anoniemerd/prunemate:latest python prunemate.py --gen-hash "your_password"This outputs a Base64-encoded hash (safe for YAML, no special characters):
c2NyeXB0OjMyNzY4Ojg6MSRvcDdnZFlGR1JmRFp4Y1RjJDBmMzNlYzc4NzExZTI4MzllYjk0MWFiOTZkOGUyZGNjNGRhMzU2NTlmMGI1ZDg0NjhjZTdkMThhODhmNmQ3ZGRhOGU4YzdmMDYxMWZiNzAyYjA0ZGNhNTBjZWMxZjFlYzc3ZjhlNzJhYmM0MmQ3OTQ5NDM2MDUzZWRlZjlhZGY0
Why Base64? Raw scrypt hashes contain
$characters that Docker Compose interprets as environment variables, corrupting the hash. Base64 encoding produces alphanumeric strings that YAML handles safely without escaping.
✅ Special characters that work well:
- Hash characters:
#- At sign:
@- Percent:
%- Asterisk:
*- Ampersand:
&- Caret:
^
⚠️ Avoid these characters:
- Exclamation mark:
!(bash history expansion)- Dollar sign:
$(variable expansion - even Base64 encoded, can cause issues in some contexts)Safe examples:
MyPassword#123Test@secure%passprunemate&admin^2024MyPass*Admin#99
- Add to your docker-compose.yaml:
environment:
- PRUNEMATE_AUTH_USER=admin # Optional (default: admin)
- PRUNEMATE_AUTH_PASSWORD_HASH=c2NyeXB0OjMyNzY4Ojg6MSRvcDdnZFlGR1JmRFp4Y1RjJDBmMzNlYzc4...- Restart the container:
docker-compose up -dImportant notes:
- Authentication is opt-in - only enabled when
PRUNEMATE_AUTH_PASSWORD_HASHis set - Without the hash variable, the app runs in open mode (backward compatible)
- For API clients (Homepage, etc.), use Basic Auth with your actual password (not the hash)
- The hash is Base64-encoded to prevent Docker Compose from interpreting
$characters as variables
Access the web interface at http://localhost:7676/ (or your server IP) to configure:
Schedule Settings:
- Frequency: Daily, Weekly, or Monthly
- Time: When to run the cleanup (supports both 12h and 24h format)
- Day: Day of week (for weekly) or day of month (for monthly)
Cleanup Options:
- ☑️ All unused containers
- ☑️ All unused images
- ☑️ All unused networks
- ☑️ All unused volumes
Notification Settings:
- Provider: Gotify, ntfy.sh, Discord, or Telegram
- Configuration: Provider-specific credentials (URL/Token for Gotify, URL/Topic for ntfy, Webhook URL for Discord, Bot Token/Chat ID for Telegram)
- Priority: Low (silent), Medium, or High priority notifications (provider-dependent)
- Only notify on changes: Only send notifications when something was actually cleaned
- Scheduler runs every minute checking if it's time to execute
- Loads latest config from persistent storage
- Executes Docker prune commands for selected resource types
- Collects statistics on what was removed and space reclaimed
- Updates all-time statistics with cumulative data (space, counts, timestamps)
- Sends notification (if configured and enabled)
- Logs everything with timezone-aware timestamps
📊 View detailed architecture & flowchart
/config/
├── config.json # Your configuration (persistent)
├── stats.json # All-time statistics (cumulative data)
├── prunemate.lock # Prevents concurrent runs
└── last_run_key # Tracks last successful run
/var/log/
└── prunemate.log # Application logs (rotating, 5MB max)
PruneMate tracks cumulative statistics across all prune runs:
Metrics tracked:
- 💾 Total Space Reclaimed - Cumulative disk space freed (displayed in MB/GB/TB)
- 📦 Containers Deleted - Total count of unused containers removed
- 🖼️ Images Deleted - Total count of unused images removed
- 🔗 Networks Deleted - Total count of unused networks removed
- 💿 Volumes Deleted - Total count of unused volumes removed
- 🔄 Total Prune Runs - Number of times prune has executed
- 📅 First Run - Timestamp of the very first prune execution
- 🕐 Last Run - Timestamp of the most recent prune execution
Technical details:
- Statistics persist in
/config/stats.jsonusing atomic writes with file locking - Updates occur after every prune run, regardless of whether resources were deleted
- Timestamps are timezone-aware and respect
PRUNEMATE_TZsetting - Date/time display in UI follows configured 12h/24h format
- Statistics survive container restarts and updates
- Auto-refresh after manual prune runs via web interface
Gotify is a self-hosted notification service.
Setup steps:
- Install and run Gotify server
- Create a new application in Gotify
- Copy the application token
- Configure in PruneMate:
- Provider: Gotify
- URL:
https://your-gotify-server.com - Token: Your application token
ntfy.sh is a simple pub-sub notification service (self-hosted or public).
Setup steps:
- Choose a unique topic name (e.g.,
prunemate-alerts) - Configure in PruneMate:
- Provider: ntfy
- URL:
https://ntfy.sh(or your self-hosted instance, supportsusername:password@hostformat) - Topic: Your chosen topic name
- Token: (Optional) Bearer token for authentication
Authentication options:
- Bearer token: Recommended for API access tokens (higher priority)
- URL credentials: Use
https://username:password@ntfy.example.comformat (RFC 3986 compliant) - No authentication: Works with public topics
Subscribe to notifications:
- Web: Visit
https://ntfy.sh/your-topic - Mobile: Install the ntfy app (Android / iOS) and subscribe to your topic
- Desktop: Use ntfy desktop app or web browser
Discord webhooks allow notifications directly to your Discord server.
Setup steps:
- Open your Discord server settings
- Go to Integrations → Webhooks
- Click New Webhook or edit existing webhook
- Copy the Webhook URL
- Configure in PruneMate:
- Provider: Discord
- Webhook URL:
https://discord.com/api/webhooks/...
Priority colors:
- Low: Green (informational)
- Medium: Orange (warning)
- High: Red (critical)
Telegram Bot API enables notifications via Telegram bots.
Setup steps:
- Open Telegram and search for @BotFather
- Send
/newbotand follow the instructions - Give your bot a name (e.g., "PruneMate Notifications")
- Give your bot a username ending in "bot" (e.g., "prunemate_notif_bot")
- Copy the Bot Token (format:
123456789:ABCdefGHIjklMNOpqrsTUVwxyz) - Get your Chat ID:
- Easy method: Message @userinfobot or @getmyid_bot to get your Chat ID
- Alternative: Message your bot, then visit
https://api.telegram.org/bot<BOT_TOKEN>/getUpdatesand find"chat":{"id":123456789}
- Configure in PruneMate:
- Provider: Telegram
- Bot Token: Your bot token from BotFather
- Chat ID: Your numeric chat ID (or
@channelnamefor channels)
Priority behavior:
- Low: Silent notifications (no sound)
- Medium/High: Normal notifications with sound
Advanced usage:
- Groups: Add bot to group, get group Chat ID (starts with
-) - Channels: Use channel username with
@(e.g.,@mychannel) or numeric ID
PruneMate can manage multiple Docker hosts from a single interface. Each prune operation runs across all enabled hosts with aggregated results.
1. Deploy proxy on each remote host:
services:
dockerproxy:
image: ghcr.io/tecnativa/docker-socket-proxy:latest
environment:
- CONTAINERS=1
- IMAGES=1
- NETWORKS=1
- VOLUMES=1
- BUILD=1 # REQUIRED FOR BUILD CACHE PRUNE
- POST=1 # Required for prune operations
ports:
- "2375:2375"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
⚠️ IMPORTANT: TheBUILD=1environment variable is REQUIRED to enable Docker build cache pruning. Without it, build cache prune operations will fail with a 403 error.
2. Add hosts in PruneMate UI:
- Navigate to Docker Hosts section
- Click Add New Host
- Enter name (e.g., "NAS") and URL (e.g.,
tcp://192.168.1.50:2375) - Toggle hosts on/off as needed
3. Test connection: Click Run now and check logs for successful connection to all hosts.
- Connection refused: Verify proxy is running (
docker ps) and port 2375 is accessible - Permission denied: Ensure proxy has
POST=1environment variable - Host skipped: Check URL format starts with
tcp://,http://, orhttps://
PruneMate provides a custom API endpoint at /api/stats that returns all-time statistics in a format compatible with Homepage dashboard widgets.
Add this configuration to your Homepage services.yaml:
- PruneMate:
href: http://<your-server-ip>:7676
description: Docker Cleanup Automation
icon: https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/prunemate.webp
widget:
type: customapi
url: http://<your-server-ip>:7676/api/stats
mappings:
- field: pruneRuns
label: Prune Runs
format: number
- field: lastRunText
label: Last Run
- field: imagesDeleted
label: Images Pruned
format: number
- field: spaceReclaimedHuman
label: Space SavedThe /api/stats endpoint returns the following fields:
| Field | Type | Description | Homepage Format |
|---|---|---|---|
pruneRuns |
number | Total number of prune operations executed | number |
containersDeleted |
number | Total containers deleted across all runs | number |
imagesDeleted |
number | Total images deleted across all runs | number |
networksDeleted |
number | Total networks deleted across all runs | number |
volumesDeleted |
number | Total volumes deleted across all runs | number |
buildCacheDeleted |
number | Total build cache entries deleted across all runs | number |
spaceReclaimed |
number | Total space reclaimed in bytes | number |
spaceReclaimedHuman |
string | Human-readable space reclaimed (e.g., "2.5 GB") | text |
lastRunText |
string | Relative time as text (e.g., "2h ago") | text |
lastRunTimestamp |
number | Unix timestamp in seconds of last run | number |
lastRun |
string | ISO timestamp of most recent prune run | date |
firstRun |
string | ISO timestamp of first prune run | date |
{
"pruneRuns": 42,
"containersDeleted": 156,
"imagesDeleted": 89,
"networksDeleted": 12,
"volumesDeleted": 7,
"buildCacheDeleted": 715,
"spaceReclaimed": 5368709120,
"spaceReclaimedHuman": "5.00 GB",
"lastRunText": "2h ago",
"lastRunTimestamp": 1733454000,
"lastRun": "2025-12-06T03:00:00+01:00",
"firstRun": "2025-01-15T03:00:00+01:00"
}| Problem | Solution |
|---|---|
| ❌ Can't access web interface | • Check if port 7676 is available and not blocked by firewall • Verify container is running: docker ps• Check logs: docker logs prunemate |
| 🏗️ ARM architecture error | • Since V1.3.1+: Image now has native multi-architecture support (amd64 + arm64) • Pull anoniemerd/prunemate:latest - it will auto-detect your platform• No local build required anymore! • If running older versions, use build: . in docker-compose.yaml |
| ⚙️ Container not starting | • View startup errors: docker logs prunemate• Verify Docker socket is accessible • Check if port 7676 is already in use |
| 🔒 Permission denied errors | • Ensure /var/run/docker.sock exists and is accessible• On Linux, Docker daemon must be running • User running Docker must have proper permissions |
| 🕐 Wrong timezone in logs/schedule | • Set PRUNEMATE_TZ environment variable correctly• Restart container after changing: docker-compose restart• Verify timezone in logs matches expected |
| 📧 Notifications not working | • Test notification settings in web interface • Verify notification server URL is accessible • Check token/topic is correct • Review logs for error messages |
| 🗂️ Configuration not persisting | • Ensure ./config volume is mounted correctly• Check file permissions on host ./config directory• Verify container has write access |
| 🧹 Cleanup not running on schedule | • Check schedule configuration in web interface • Verify timezone is set correctly • Review logs: "Next scheduled run" messages • Ensure container is running continuously |
What the logs contain:
- ✅ Scheduler heartbeats (every minute)
- 📝 Configuration changes
- 🧹 Prune job executions with results
- 📨 Notification delivery status
- ❌ Error messages and warnings
- 🏗️ New info button - A new information button has been added to the newly launched Prunemate.org website.
Have questions or need help?
- 🐛 Bug reports: Open an issue on GitHub
- 💡 Feature requests: Open an issue on GitHub
- 💬 Questions & Discussion: Start a discussion on GitHub
- ⭐ Like PruneMate? Give it a star!
If you find PruneMate useful and would like to support the development, consider buying me a coffee!
Your support helps me dedicate more time to maintaining and improving PruneMate! ❤️
Author: Anoniemerd
🐙 GitHub: https://github.com/anoniemerd
📦 Repository: https://github.com/anoniemerd/PruneMate
I'm grateful for the contributions that make PruneMate better!
- @difagume - 🔐 Authentication system implementation (V1.3.0)
- @shollyethan - 🎨 Logo redesign & added the logo to Self-Hosted Dashboard Icons
- anoniemerd - Project creator and maintainer
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
By using, modifying, or distributing this software, you must:
- Keep this copyright notice
- Disclose source code of any modified version
- Disclose source code if used to provide a network service
- License any derivative works under AGPL-3.0
See the full license text in: LICENSE
USE AT YOUR OWN RISK. PruneMate is provided "as is" without warranty of any kind. The author(s) and contributors are not responsible for:
- Data loss from pruned Docker resources
- Service interruptions or downtime
- System instability or performance issues
- Any damages resulting from the use or misuse of this software
Always:
- ✅ Understand which resources will be deleted
- ✅ Keep backups of important data and configurations
- ✅ Review logs after prune operations
- ✅ Start with conservative settings
© 2026 – PruneMate Project
Keep your Docker host clean with PruneMate! 🐳🧹










