Prune old images, and keep every release #246

Closed
opened 2026-09-03 06:47:20 +00:00 by jcoffey-dev · 0 comments
Owner

Follow-up to #244 and #245. Two artefacts, opposite answers.

Releases stay — all of them

They carry no assets (v2026.9.2-pr244 has zero; the image lives in GHCR), so a release is a tag, a title and generated notes. And there is no CHANGELOG file in this repository, which makes those generated notes the only changelog the project has.

Deleting one destroys history that cannot be reconstructed and saves nothing. Rollback would survive — deleting a release does not delete its tag — but the record of what was in it would not.

Images get pruned

A multi-arch build a week, and the by-digest push in publish.yml leaves two untagged per-architecture manifests behind each time on top of the tagged index. Ten tagged versions kept — roughly a quarter of a year, and far more than anything anyone rolls back to.

The trap

The obvious tool is actions/delete-package-versions with delete-only-untagged-versions: true. It will delete the per-architecture manifests that a multi-arch tag points at, because they are untagged by design.

Nothing appears to break. The tag still resolves. Pulls just start failing for one architecture, at some later date, for a reason nobody connects to a cleanup job that ran weeks earlier.

dataaxiom/ghcr-cleanup-action understands manifest lists and will not orphan a retained index. On top of that:

  • validate: true re-checks every remaining multi-arch manifest against the registry after the sweep — the step that would catch the footgun rather than leave it to be discovered on docker pull
  • exclude-tags: latest takes latest out of consideration under any counting
  • delete-partial-images / delete-orphaned-images sweep the wreckage of a half-failed run

Pinned to a commit

@d52806a0 (v1.2.2), not @v1. This action is handed packages: write and its entire purpose is deletion; a tag repointed upstream — by compromise or mistake — is not a risk worth carrying for the convenience of a floating major.

Its own file, on purpose

cleanup.yml is workflow_call and workflow_dispatch, with dry_run defaulting to true on manual runs. That means the deletion list can be inspected without rebuilding and re-pushing an image to trigger it.

Testing

actionlint clean across all three workflows. After merge I'll dispatch it with dry_run: true and read exactly what it proposes to delete before anything runs for real — and confirm both architectures still resolve afterwards.

Merged 2026-09-02 as coffey-labs/ihasmail@3ffee1224f

Rebuilt from: git history, session transcript.

Follow-up to #244 and #245. Two artefacts, opposite answers. ## Releases stay — all of them They carry **no assets** (`v2026.9.2-pr244` has zero; the image lives in GHCR), so a release is a tag, a title and generated notes. And there is **no `CHANGELOG` file in this repository**, which makes those generated notes the only changelog the project has. Deleting one destroys history that cannot be reconstructed and saves nothing. Rollback would survive — deleting a release does not delete its tag — but the record of what was in it would not. ## Images get pruned A multi-arch build a week, and the by-digest push in `publish.yml` leaves **two untagged per-architecture manifests** behind each time on top of the tagged index. Ten tagged versions kept — roughly a quarter of a year, and far more than anything anyone rolls back to. ## The trap The obvious tool is `actions/delete-package-versions` with `delete-only-untagged-versions: true`. It will delete the **per-architecture manifests that a multi-arch tag points at**, because they are untagged by design. Nothing appears to break. The tag still resolves. Pulls just start failing for one architecture, at some later date, for a reason nobody connects to a cleanup job that ran weeks earlier. `dataaxiom/ghcr-cleanup-action` understands manifest lists and will not orphan a retained index. On top of that: - `validate: true` re-checks every remaining multi-arch manifest against the registry after the sweep — the step that would *catch* the footgun rather than leave it to be discovered on `docker pull` - `exclude-tags: latest` takes `latest` out of consideration under any counting - `delete-partial-images` / `delete-orphaned-images` sweep the wreckage of a half-failed run ## Pinned to a commit `@d52806a0` (v1.2.2), not `@v1`. This action is handed `packages: write` and its entire purpose is deletion; a tag repointed upstream — by compromise or mistake — is not a risk worth carrying for the convenience of a floating major. ## Its own file, on purpose `cleanup.yml` is `workflow_call` **and** `workflow_dispatch`, with `dry_run` defaulting to **true** on manual runs. That means the deletion list can be inspected without rebuilding and re-pushing an image to trigger it. ## Testing `actionlint` clean across all three workflows. After merge I'll dispatch it with `dry_run: true` and read exactly what it proposes to delete before anything runs for real — and confirm both architectures still resolve afterwards. **Merged** 2026-09-02 as coffey-labs/ihasmail@3ffee1224fe7 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.