Creation is the photogenic part. The adult work begins when every artifact gets a deadline, an owner, protection, and a safe way to leave the stage.
TL;DR
Automating creation without automating the lifecycle transfers cost, risk, and confusion to the future. Container images, previews, temporary working branches, build artifacts, and backups should not exist forever simply because nobody defined their end. Safe cleanup is not deleting everything that looks old: it means consulting a source of truth, protecting production, keeping a path back to a version known to work — a rollback —, applying a retention policy, simulating before removing, requiring evidence of disuse, and preserving a recovery path. If the automation cannot distinguish operational memory from abandonment, it is not complete yet.
Today I encountered, once again, a scene that is anything but rare in technology.
An operation was still paying to store software images that had not been used in more than five years.
Not because someone had deliberately preserved five years of history. Not because there was an audit requirement, a recovery window, or an old version that could still save production.
They remained because nobody could state, safely, what could be deleted.
So nobody deleted anything.
It is a curious form of governance: doubt becomes the retention policy, and the bill becomes the documentation.
The pipeline got automation. The warehouse did not
We are very good at automating beginnings.
A new versioned change — a commit — triggers an automated build. The build generates a package. The package becomes an image. The image receives a tag: a name such as 1.4 or latest that references its manifest in the image registry. The temporary version gets a review address, or preview. The test produces a report, an execution record — the log —, temporary copies meant to speed up the next run — a cache —, and other artifacts. Everything happens in minutes, sometimes seconds.
Then the work ends — for us.
For the infrastructure, it has only begun taking up space.
Every successful run leaves something behind. One leftover looks irrelevant. Thousands of them, accumulated over years, start consuming storage, obstructing inventory, preserving components that might need removal for security reasons, and making any future cleanup more frightening.
The problem appears because we treat creation as a workflow and disposal as housekeeping.
But operational cleanup should not be the embarrassed task force assembled when a disk fills up. It should be part of the same lifecycle that authorized the artifact to exist.
If an automation can create ten versions a day but cannot tell when nine of them no longer serve a purpose, it has not eliminated work.
It has sent the bill into the future, with interest charged in lost context.
Old is not the same as useless
The lazy answer would be to pick an age and delete everything older than that.
It would be fast. It would also be an efficient way to discover, too late, that the “old” version was the last working version available for rollback, that a branch office still depended on it, or that the lovingly stored backup could not restore anything.
Age is a signal. It is not a decision.
Retention may be necessary for different reasons:
- continuity: keeping the production version and a few versions proven usable for rollback;
- traceability: preserving the artifact tied to a delivery, incident, or decision that still needs to be audited;
- obligation: meeting a legal, contractual, or internal rule with a defined period and owner;
- recovery: keeping data and configuration for as long as restoration remains useful;
- investigation: retaining evidence while a failure or incident remains open.
That is retention.
Finding hundreds of versions with no known consumer, owner, expiration date, or proof that anyone could use them is something else.
That is not institutional memory.
It is abandonment with geographic redundancy.
A container image is not an isolated box
When I say image here, I am not talking about a photograph. A container image is a reproducible package used to start an application with its dependencies and system configuration.
Nor is it necessarily an independent block.
The Open Container Initiative (OCI) specification calls the image descriptor a manifest: it points to the image configuration and to reusable blocks of content called layers. Identical layers may be shared by several versions. Removing a tag, therefore, does not automatically release every byte that a dashboard appeared to assign to that version.
In a registry operated with Distribution — open-source software from the Cloud Native Computing Foundation (CNCF) for storing and distributing container images —, garbage collection removes content blocks that are no longer referenced by any manifest. The official documentation provides a dry-run precisely so operators can see what would be eligible before any data is removed.
That simulation, however, does not make the real run safe while the registry is still accepting data. Distribution requires the registry to be read-only or completely stopped during collection: a concurrent upload can have layers deleted by mistake and end as a corrupted image. Stop writes first; collect second. Reversing that order is backing up hope and calling it a procedure.
That technical distinction reinforces a larger idea:
“It looks unused” and “it can be safely removed” are not the same sentence.
Docker itself takes a conservative approach to cleanup: unused objects do not disappear automatically, and the scope of prune commands changes with the selected options. Tooling caution does not replace company policy. It merely prevents an aggressive default from making a decision that infrastructure cannot understand on its own.
Typing a command with --force — which, in this Docker command, only skips confirmation — does not create context.
It only removes the tool’s last chance to ask whether anyone thought first.
The same waste gets different names
Container images make the problem visible because storage appears on the bill. But the same missing lifecycle spreads across an operation:
- review previews: every candidate version gets a temporary address, even after the change has been closed;
- branches: separate working versions remain in the repository after a decision was incorporated or abandoned;
- build artifacts: packages, reports, logs, and intermediate results outlive their usefulness;
- caches: they accelerate the next job until they become an expensive collection of pasts that will never return;
- temporary environments: they are born for a two-day test and discover administrative immortality;
- backups: copies accumulate without classification, restoration tests, a deadline, or a clear relationship to the current system.
GitHub lets teams set retention periods for artifacts produced by workflows and automatically delete branches whose changes have already been merged. Cloudflare Pages documents how to delete old previews while preserving the latest deployment associated with a branch.
The tools provide mechanisms.
None of them understands the whole policy of your operation on its own.
An old preview may be waste. It may also be the review environment for a change that is still open and linked to a branch. A branch associated with a closed change may be disposable. It may also be the base of another change. Yesterday’s backup may be sufficient. It may also have copied the day-before-yesterday’s corruption perfectly.
Deleting by age without consulting actual state is replacing accumulation with Russian roulette.
Find the source of truth first
Before building the broom, I need to know where operational truth lives.
Which image is actually deployed? Which production versions exist? Which changes or pull requests are still open, and which branches are associated with them? Which preview represents each change? Which packages were published as releases? Which of those versions are still supported? Which restoration was tested? Is there a legal hold? Is an incident preserving evidence?
Those answers rarely live in one place.
The common mistake is appointing one isolated dashboard as the universal judge. The image registry knows which versions it stores, but it may not know what runs in another environment. The preview provider knows about deployments, but not necessarily the status of the review. The repository knows its branches, but not every consumer of an image.
A safe routine needs to compose those sources.
If it cannot confirm what is active, protected, or still under review, it should fail safely: CWE-636 is an entry in MITRE’s Common Weakness Enumeration, a catalog for classifying software weaknesses; in this context, the principle means deleting nothing and explaining which evidence is missing instead of assuming that missing data authorizes deletion.
The inconvenience of reviewing a report is smaller than the elegance of automating a disaster.
The cleanup contract I trust
I do not trust a cleanup routine because it has an elegant automated schedule — a cron job standardized by the POSIX (Portable Operating System Interface) family — or because it freed a lot of space in a test.
I trust it when it can answer these questions:
- Who owns it? Someone is responsible for the policy, exceptions, and periodic review.
- What is the source of truth? Production, supported versions, open changes, and mandatory retention are discovered in defined systems.
- What is protected? Production, the last known-good version, the configuration and artifacts required to return to it, maintained releases, and retained evidence form a set that does not depend on age alone.
- What is the retention window? Each class gets its own period. A preview need not follow the same rule as a backup; a log need not imitate a release.
- Is there evidence of disuse? The absence of a reference, deployment, existing branch, or consumer matters more than “it has been a while.”
- Is the first run a simulation? The routine lists what it would remove, under which rule, and with what estimated impact before touching data.
- Is the impact bounded? Maximum counts, minimum age, batched execution, and a pause on divergence prevent one failure from sweeping everything away.
- Is there a way back? When the platform offers restoration, the window must be known. When it does not, the protected set must be sufficient to rebuild.
- Does it leave a trace? The run records rule, identifier, reason, time, and result without dumping secrets into logs.
- Is the policy reusable? When several projects share a provider and the same protections, the logic should live in one central, tested, versioned workflow instead of gaining divergent copies.
GitHub calls that last capability a reusable workflow: one central workflow can be called by several repositories, reducing duplication and concentrating maintenance. Reuse does not mean pretending every project is identical. It means centralizing common logic and making differences explicit as inputs.
A good policy also starts small.
Inventory first. Simulate next. Then remove one low-risk class, observe the result, and only then expand the scope.
That is less cinematic than running “clean everything.”
It is also considerably better for anyone hoping to remain employed the next morning.
A backup without a lifecycle can become accumulation too
I advocate for backups. I have already written that backup is method and the freedom to preserve options.
But “keep everything forever” is not a recovery strategy.
A backup set needs to state:
- what was copied;
- how often;
- how long each class remains;
- where an independent copy exists;
- how integrity and restoration are tested;
- which keys, configuration, and documentation recovery requires;
- when a special hold prevents normal disposal.
Without that, backups can occupy space indefinitely and still fail at the only moment that matters.
The distinction is simple: retention preserves a known capability; accumulation preserves a doubt.
Lifecycle is part of delivery
For a long time, we treated removal as a secondary concern. The good team was the one that put things online. Retirement could wait until somebody had time, storage complained, or an audit asked why something everyone swore had been deleted still existed.
That view is incomplete.
Delivering a system also means knowing how to update, observe, recover, retire, and remove its artifacts. Crossing the whole pipeline — requirements, architecture, implementation, infrastructure, monitoring, security, backup, and continuity — taught me to distrust solutions that show only the birth.
At i-9.ai, this care becomes an operational contract: what the automation may create, how we verify that it still serves a purpose, who decides its retention, and under which conditions it may leave without taking production with it.
This is not an obsession with deleting.
It is the opposite: knowing why everything continues to exist.
Because if every pipeline ends with “keep it and look later,” the future stops being a direction.
It becomes a warehouse.
And a warehouse without inventory is just a bill with aisles.
Further reading
Official documentation consulted on August 30, 2026:
- Open Container Initiative — Image Manifest Specification: the technical structure of image manifests, configuration, and layers.
- Open Container Initiative — Distribution Specification: defines how named tags reference manifests in a registry; it does not determine a company’s retention policy.
- Git — git commit: how a versioned change records repository history.
- GitHub Actions — Dependency caching: the distinction between reusable caches and artifacts kept after a run, including built binaries and build logs.
- Docker Docs — Prune unused Docker objects: mechanisms and scope differences for cleaning unused Docker objects.
- Cloud Native Computing Foundation (CNCF) Distribution — Garbage collection: reference marking, eligible objects, dry-run mode, and the requirement to prevent writes during collection to avoid corrupted images.
- The Open Group — crontab: a specification in the POSIX (Portable Operating System Interface) family of portable operating-system interface standards, applied to periodic background work.
- MITRE — CWE-636: Not Failing Securely: entry 636 in the Common Weakness Enumeration (CWE), MITRE’s catalog of software weaknesses; it explains the risk of a failure moving a system into a less secure state.
- Ubuntu — Release cycle: a concrete example of published versions with different support windows, showing why “released” and “still supported” are separate states.
- Amazon Web Services (AWS) CodeDeploy — Redeploy and roll back a deployment: a concrete example of returning to an application revision known to work.
- GitHub Docs — Reusing workflow configurations: how to centralize repeatable logic in reusable workflows.
- Cloudflare Pages — Preview deployments: preview lifecycle, branch aliases, and deletion of old deployments.
References and limits of use
- Docker’s documentation supports the claim that unused objects are not removed automatically and explains the scope of prune commands. It does not define the right retention policy for a particular company.
- Git and GitHub Actions documentation support the vocabulary of commits, artifacts, logs, and caches in their respective ecosystems. They do not define the lifecycle of every build and publishing tool.
- The OCI specification and Distribution documentation support the relationship between manifests, referenced layers, and garbage collection in this registry software, including the corruption risk during concurrent uploads. Other registries may implement deletion, retention, and restoration differently.
- The POSIX specification supports the meaning of a
cronjob; it does not make the scheduled work safe. - CWE-636 supports the general principle of not moving into a less secure state during failure. Applying it to cleanup — refusing deletion when evidence is missing — is the article’s operational synthesis.
- Ubuntu’s lifecycle illustrates that publication and support have different dates and states. It does not define every product’s lifecycle policy.
- AWS CodeDeploy documentation supports the example of returning to an application revision known to work. It does not define universal behavior: the scope and mechanics of recovery vary by platform.
- GitHub’s documentation supports artifact retention settings, automatic deletion of branches associated with merged changes, and workflow reuse. It does not prove savings or guarantee that a generic rule recognizes every external consumer.
- Cloudflare’s documentation supports Pages preview behavior and deletion. The example does not authorize deleting a review environment whose branch or operational role remains active.
- The case of artifacts retained for more than five years is an anonymized authorial observation. It illustrates a missing lifecycle; it does not establish average cost, universal waste, or the financial return of a cleanup routine.

Open conversation
Continue the conversation
Disagree, spot a gap, or have an experience that adds to the subject? Comment with your GitHub account. Do not publish personal data, credentials, or sensitive information.