Appearance
nkapp images prune
Reclaim Docker image space. The image analogue of nkapp volumes prune and nkapp containers prune, reusing the same selection authority the Hub runs.
bash
nkapp images prune [options]A side-effect-free preview is always rendered first; --dry-run stops there. When nothing is prunable, the preview names which guard is holding the space, so the next step is obvious rather than guessable.
Why the default scope is narrow
Images are the most dangerous of the three objects to get wrong. A wrongly deleted container is an annoyance; a wrongly deleted image is a multi-hundred-megabyte re-pull that can block a deploy outright on a metered or offline connection.
So the default is the narrowest scope that is safe to run without thinking, and every widening is an explicit flag that loosens exactly one guard.
| Scope | Flag | What it adds |
|---|---|---|
| dangling only | (default) | untagged <none>:<none> layers orphaned by rebuilds — nothing can reference them by name |
| + unused tagged | --unused | tagged images no container references. This is where the space is, and where the risk is. |
| + own build artifacts | --include-build-artifacts | this project's own pushed registry images (ECR/ACR/GAR/DOCR). The remote registry still holds them, so the cost is a rebuild, not a lost artifact. |
| + recent | --older-than 0 | images newer than the 24h floor |
Guards with no opt-out
There is no legitimate reason to remove either of these, so no flag reaches them:
- In use by a container, running or stopped. A stopped container whose image is gone can never restart. Matched on both the resolved image ID and the reference the container was created from — either alone has a blind spot — and a bare
mariadbreference protectsmariadb:latestand vice versa. - Declared in
nanokit.yml, for any environment. A declared image with no container right now is not garbage: it is what the nextupneeds. The resolver walks the root block and everyenvironments.<env>override, because config loading deep-merges only the active environment — aenvironments.productionimage would otherwise look like garbage during a-e localprune. It also resolves services that declare noimage:at all (viaruntimes:, then the built-in runtime defaults), database engine defaults, cron images, and the system images Nanokit uses (the Caddy gateway, the cron scheduler, the bridge's Node image, backup tool images).
Multi-tag images are all-or-nothing
One image ID can carry many tags, and docker rmi <id> takes them all. So an image is prunable only when every tag is prunable. That is not just the safe choice, it is the only effective one: untagging some tags of an image that survives frees zero bytes.
Removal is tag-by-tag by name and never forced, so a container that appears between the preview and the run makes the daemon refuse rather than destroy.
Honest reclaim reporting
Docker's Size counts layers shared with images that will survive, so summing it over-reports — often threefold. The preview therefore reports a range:
reclaims at least 9.4 GB, up to 32.9 GB if shared layers free upThe floor is the bytes unique to the prunable set, and it is guaranteed.
Options
| Flag | Type | Default | Description |
|---|---|---|---|
-e, --env <env> | string | local | Environment to use |
--ssh <host> | string | — | Target an explicit host, overriding the environment's configured deploy target |
-p, --port <port> | number | 22 | SSH port (used with --ssh) |
-i, --ssh-key <path> | string | — | SSH private key override (used with --ssh) |
--older-than <dur> | string | 24h | Only prune images created at least this long ago — s/m/h/d, or 0 to disable |
--unused | boolean | false | Also prune tagged images no container references |
--include-build-artifacts | boolean | false | Also prune this project's own pushed registry images |
--dry-run | boolean | false | List what would be pruned, change nothing |
-y, --yes | boolean | false | Skip the confirmation prompt |
An unparseable --older-than value fails before any connection is opened.
Not wired into reconcile
up and deploy do not run this prune — removing an image mid-up converts a fast reconcile into a registry round-trip, which is exactly the failure this command exists to avoid. Detection is automatic (nkapp doctor → Image Space); removal stays explicit.
Build cache is out of scope by design
BuildKit's build cache is a different Docker object with different semantics: it owns its own GC policy, entries are keyed by build step rather than by name, nothing in nanokit.yml can reference one, and none of this command's guards has any meaning for it. Folding it in would mean one flag whose safety contract is unrelated to every other flag here. Use docker builder prune for that, or see your BuildKit GC configuration.
Examples
bash
# The safe default — dangling layers only
nkapp images prune --dry-run
nkapp images prune -y
# Where the space actually is; review the table first
nkapp images prune --unused --dry-run
# Everything reclaimable, including this project's registry artifacts
nkapp images prune --unused --include-build-artifacts --dry-run
# Reclaim space on a remote host
nkapp images prune --ssh root@1.2.3.4 --unused --dry-runExecution is best-effort: per-image failures are aggregated and reported, and any failure exits non-zero. Ctrl+C closes the SSH tunnel cleanly.