Skip to content

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.

ScopeFlagWhat it adds
dangling only(default)untagged <none>:<none> layers orphaned by rebuilds — nothing can reference them by name
+ unused tagged--unusedtagged images no container references. This is where the space is, and where the risk is.
+ own build artifacts--include-build-artifactsthis 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 0images 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 mariadb reference protects mariadb:latest and vice versa.
  • Declared in nanokit.yml, for any environment. A declared image with no container right now is not garbage: it is what the next up needs. The resolver walks the root block and every environments.<env> override, because config loading deep-merges only the active environment — a environments.production image would otherwise look like garbage during a -e local prune. It also resolves services that declare no image: at all (via runtimes:, 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 up

The floor is the bytes unique to the prunable set, and it is guaranteed.


Options ​

FlagTypeDefaultDescription
-e, --env <env>stringlocalEnvironment to use
--ssh <host>string—Target an explicit host, overriding the environment's configured deploy target
-p, --port <port>number22SSH port (used with --ssh)
-i, --ssh-key <path>string—SSH private key override (used with --ssh)
--older-than <dur>string24hOnly prune images created at least this long ago — s/m/h/d, or 0 to disable
--unusedbooleanfalseAlso prune tagged images no container references
--include-build-artifactsbooleanfalseAlso prune this project's own pushed registry images
--dry-runbooleanfalseList what would be pruned, change nothing
-y, --yesbooleanfalseSkip 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-run

Execution is best-effort: per-image failures are aggregated and reported, and any failure exits non-zero. Ctrl+C closes the SSH tunnel cleanly.