A compose-aware Docker cleanup utility that removes resources tied to the project in your current directory — containers, images, volumes, and networks — and can also prune Docker build cache (global scope).
- Why this exists
- How it works
- Requirements
- Installation
- Usage
- Cleanup steps explained
- Project name resolution
- Supported compose file names
- Dry-run mode
- Global prune mode
- Exit codes
- Safety notes
- FAQ
- License
Running docker compose down is often not enough. It stops containers and removes the default network, but it leaves behind:
- Named volumes (unless you pass
-v) - Images built or pulled for the project (unless you pass
--rmi all) - Orphaned containers from previous runs
- The Docker build cache, which can grow to many gigabytes over time
Passing every flag every time is tedious and error-prone. docker-prune.sh wraps all of that into a single command with sensible defaults, coloured output, and a dry-run mode so you always know exactly what will be deleted before it happens.
The script performs seven sequential cleanup steps, all scoped to the Compose project in the current directory:
Step 1 → Stop all running project containers
Step 2 → docker compose down (removes containers, networks, volumes, images)
Step 3 → Force-remove any orphaned containers by project label
Step 4 → Force-remove any remaining project-labelled images
Step 5 → Force-remove any remaining project-labelled networks
Step 6 → Force-remove any remaining project-labelled volumes
Step 7 → docker builder prune (clears the build cache)
Each step reports its status with coloured log output ([INFO], [ OK ], [WARN], [ERR ]). If a step finds nothing to clean up it skips gracefully — no errors, no noise.
| Requirement | Minimum version | Notes |
|---|---|---|
| Bash | 4.0+ | Available on all modern Linux distros and macOS with Homebrew |
| Docker Engine | 20.10+ | Required for label-based filtering |
| Docker Compose | V2 (docker compose) or V1 (docker-compose) |
Script auto-detects which is available |
The script checks for all dependencies at startup and exits with a clear error message if anything is missing.
Place docker-prune.sh directly in the root of your project, next to your docker-compose.yml:
my-project/
├── docker-compose.yml
├── docker-prune.sh ← here
├── backend/
└── frontend/
Copy the script to a directory on your PATH so it is available from any project:
sudo cp docker-prune.sh /usr/local/bin/docker-prune
sudo chmod +x /usr/local/bin/docker-pruneThen call it from any directory that contains a compose file:
cd ~/projects/my-app
docker-prunechmod +x docker-prune.sh# Run from the directory containing your docker-compose.yml
./docker-prune.shWithout any flags the script will:
- Detect your compose file and project name.
- Show a confirmation prompt listing what will be deleted.
- Execute all seven cleanup steps on confirmation.
Usage:
docker-prune.sh [OPTIONS]
Options:
-h, --help Show help and exit
-n, --dry-run Print every command that would run — nothing is deleted
-f, --force Skip the confirmation prompt (useful in CI/CD)
--no-volumes Keep named volumes, skip volume removal steps
--no-images Keep images, skip image removal steps
--no-networks Keep project networks. Note: 'compose down' still removes the default project network; this flag only skips the label-based network sweep.
--no-cache Skip 'docker builder prune'
--global After project cleanup, also run 'docker system prune'
to remove ALL dangling resources across every project
# Preview everything that would be deleted (safe — nothing is changed)
./docker-prune.sh --dry-run
# Full wipe, no prompt — ideal for CI pipelines or reset scripts
./docker-prune.sh --force
# Wipe everything except the database volume (so your data survives)
./docker-prune.sh --no-volumes
# Wipe containers and networks only — keep images to avoid re-pulling
./docker-prune.sh --no-images --no-cache
# Nuclear option: wipe this project AND all dangling Docker resources globally
./docker-prune.sh --global --force
# Combine flags freely
./docker-prune.sh --no-volumes --no-cache --forcedocker compose -f <file> stopGracefully stops all containers that are currently running in the project. Skipped automatically if no containers are running.
docker compose -f <file> down --remove-orphans [--volumes] [--rmi all]The core teardown command. Removes containers, the default project network, and (unless --no-volumes or --no-images are passed) volumes and images declared in the compose file. --remove-orphans cleans up containers from services that no longer exist in the compose file.
docker rm -f $(docker ps -a --filter "label=com.docker.compose.project=<name>" -q)Catches any containers that compose down missed — for example containers that were started manually with docker run but tagged with the project label, or containers left behind from a crashed previous run.
docker rmi -f $(docker images --filter "label=com.docker.compose.project=<name>" -q)Removes images that carry the project label. This catches images built locally with docker compose build that compose down --rmi all may not fully clean up, for example when image names were changed between runs.
docker network rm $(docker network ls --filter "label=com.docker.compose.project=<name>" -q)Removes any remaining project-labelled networks after compose down. The
default project network created by Compose is still removed by compose down
even when --no-networks is set.
docker volume rm -f $(docker volume ls --filter "label=com.docker.compose.project=<name>" -q)Removes named volumes that carry the project label. Complements compose down --volumes, which only removes volumes declared in the compose file's volumes: section — this sweep catches any volumes created at runtime.
docker builder prune -fClears the entire Docker BuildKit cache. This is a global operation (Docker does not expose per-project cache filtering), so it affects cached layers from all projects. It is the single fastest way to reclaim large amounts of disk space after intensive builds.
Tip: Use
--no-cacheif you share a build host with other teams and want to avoid evicting their cache layers.
The script derives the project name using Compose-style logic, with one caveat:
- If the environment variable
COMPOSE_PROJECT_NAMEis set, that value is used. - Otherwise, the name defaults to the basename of the current working directory.
It does not parse a top-level name: from the compose file. If you rely on that,
set COMPOSE_PROJECT_NAME explicitly when running.
# Override the project name without renaming your directory
COMPOSE_PROJECT_NAME=myapp ./docker-prune.shThis matters for steps 3–6, which filter Docker resources by the label com.docker.compose.project=<name>. If the project name does not match what Compose used when the stack was originally created, those steps will find nothing — which is safe (no false deletions), but you may need to set COMPOSE_PROJECT_NAME explicitly to match the original name.
The script checks for these filenames in order and uses the first one it finds:
| Priority | Filename |
|---|---|
| 1 | docker-compose.yml |
| 2 | docker-compose.yaml |
| 3 | compose.yml |
| 4 | compose.yaml |
If none are present in the current directory the script exits immediately with an error. It will never silently operate on the wrong directory.
Dry-run mode (-n / --dry-run) is the safest way to explore what the script would do:
./docker-prune.sh --dry-runEvery command that would be executed is printed to stdout prefixed with [dry-run]. No Docker resources are created, modified, or deleted. The compose file detection, project name resolution, and dependency checks still run normally, so any configuration problems are surfaced without risk.
Example output:
╔══════════════════════════════════════════════╗
║ Docker Compose — Prune Utility ║
╚══════════════════════════════════════════════╝
[INFO] Compose file : docker-compose.yml
[INFO] Project name : my-app
[INFO] Dry-run mode : true
──────────────────────────────────────────────────
[INFO] Step 1/7 — Stopping running containers …
[dry-run] docker compose -f docker-compose.yml stop
──────────────────────────────────────────────────
[INFO] Step 2/7 — Removing containers and project networks …
[dry-run] docker compose -f docker-compose.yml down --remove-orphans --volumes --rmi all
──────────────────────────────────────────────────
[INFO] Step 3/7 — Checking for orphaned containers …
[dry-run] docker rm -f <ids>
...
Dry-run complete — nothing was deleted.
The --global flag appends a docker system prune call after the project-scoped cleanup:
./docker-prune.sh --global --forceThis removes all unused Docker resources on the host — dangling images, stopped containers, unused networks, and (when combined without --no-volumes) all unused volumes — regardless of which project they belong to.
⚠️ Use with caution on shared hosts. Global prune affects every Docker project on the machine, not just the current one. It is most appropriate on development machines or dedicated single-project CI runners.
| Code | Meaning |
|---|---|
0 |
Success, or dry-run completed, or user chose to abort at the prompt |
1 |
Fatal error — missing compose file, missing dependency, or unknown flag |
Confirmation prompt — without --force, the script always asks before deleting anything. Type y or yes to proceed; anything else (including pressing Enter without typing) aborts cleanly.
Scope — steps 1–6 are strictly scoped to the current project by Docker label. Resources belonging to other projects are never touched unless --global is passed.
Build cache — step 7 (docker builder prune) is the only inherently global step. Pass --no-cache to preserve cache layers shared with other projects on the same host.
Irreversibility — deleted volumes and their contents cannot be recovered. Always run --dry-run first if you are unsure, and ensure important data (e.g. database volumes) is either backed up or excluded with --no-volumes.
set -euo pipefail — the script runs with strict error handling. Any unexpected command failure causes an immediate exit rather than silently continuing.
Q: The script says "No project-labelled images/volumes found" even though they exist.
Compose only attaches the com.docker.compose.project label to resources it manages directly. Images pulled externally (e.g. with docker pull) or volumes created outside of Compose will not carry the label. Use docker rmi / docker volume rm to remove them manually, or run with --global to catch all dangling resources system-wide.
Q: Can I use this in a CI/CD pipeline?
Yes. Pass --force to suppress the interactive prompt:
# GitHub Actions example
- name: Clean up Docker environment
run: ./docker-prune.sh --forceQ: I renamed my project directory and now the label filter finds nothing.
Set COMPOSE_PROJECT_NAME to the name that was in use when the stack was first started:
COMPOSE_PROJECT_NAME=old-name ./docker-prune.shQ: Does this work with Docker Compose V1 (docker-compose)?
Yes. The script auto-detects whether docker compose (V2, the CLI plugin) or docker-compose (V1, the standalone binary) is available, preferring V2. If neither is found it exits with a clear error.
Q: Will this remove volumes from other projects?
Usually no — unless you pass --global or another stack on the same host uses the same Compose project name. Steps 3–6 filter by com.docker.compose.project=<your-project-name>, so set a unique COMPOSE_PROJECT_NAME per stack to avoid overlap.
Q: What if my compose file is in a subdirectory?
cd into the directory containing the compose file before running the script. The script always operates on the current working directory.
MIT — see LICENSE for details.