Sweep the snapshot commits recreation leaves behind #23

Merged
jknapp merged 1 commits from sweep-orphaned-snapshots into main 2026-08-12 02:06:24 +00:00
Owner

Every recreation commits the container to triple-c-snapshot-{id}:latest and moves that tag; the image it pointed at keeps its layers and loses its name. Nothing deleted those. Measured on a real host: 7 orphans holding 7.4 GB, three of them from a single day's work.

The safety argument

sweep_orphaned_snapshots removes an image only when it is both:

  • untagged — every image the app depends on carries a tag, so a project's live triple-c-snapshot-{id}:latest and a migration's pre-migration-* rollback pin cannot match the filter at all; and
  • labelled triple-c.managed=true — which docker commit copies from the container onto the image, making it a reliable mark of provenance. The user's own dangling images are not ours to delete.

Removal is unforced on top of that, so Docker refuses (409) while any container is still built from the image — including the stopped containers of projects that are not running. Those are counted and left for the next sweep. A test pins both filter conditions in place, because losing either one turns a snapshot sweep into a prune of the user's whole image store.

When it runs

  • After a recreation — the container holding the old image open has just been removed, so this is when Docker will actually let it go.
  • After a migration is accepted — dropping the rollback pin is precisely what turns the pre-migration snapshot into an orphan. Waiting for that project's next recreation would leave it lying around indefinitely.

Both detached: this is housekeeping, and a full disk is a better outcome than a project that will not start. Each sweep clears every orphan it finds, not just the one it caused, so recreations that predate this change are cleaned up too.

Also: the triple-c.managed label string is now a constant rather than four literals, and the README's Container Lifecycle section documents the behaviour.

Tests

22/22 in docker::container (including the new filter test), 348/348 frontend, cargo check clean with no warnings.

🤖 Generated with Claude Code

Every recreation commits the container to `triple-c-snapshot-{id}:latest` and moves that tag; the image it pointed at keeps its layers and loses its name. Nothing deleted those. Measured on a real host: **7 orphans holding 7.4 GB**, three of them from a single day's work. ### The safety argument `sweep_orphaned_snapshots` removes an image only when it is **both**: - **untagged** — every image the app depends on carries a tag, so a project's live `triple-c-snapshot-{id}:latest` and a migration's `pre-migration-*` rollback pin cannot match the filter at all; and - **labelled `triple-c.managed=true`** — which `docker commit` copies from the container onto the image, making it a reliable mark of provenance. The user's own dangling images are not ours to delete. Removal is unforced on top of that, so Docker refuses (409) while any container is still built from the image — including the stopped containers of projects that are not running. Those are counted and left for the next sweep. A test pins both filter conditions in place, because losing either one turns a snapshot sweep into a prune of the user's whole image store. ### When it runs - **After a recreation** — the container holding the old image open has just been removed, so this is when Docker will actually let it go. - **After a migration is accepted** — dropping the rollback pin is precisely what turns the pre-migration snapshot into an orphan. Waiting for that project's next recreation would leave it lying around indefinitely. Both detached: this is housekeeping, and a full disk is a better outcome than a project that will not start. Each sweep clears every orphan it finds, not just the one it caused, so recreations that predate this change are cleaned up too. Also: the `triple-c.managed` label string is now a constant rather than four literals, and the README's Container Lifecycle section documents the behaviour. ### Tests 22/22 in `docker::container` (including the new filter test), 348/348 frontend, `cargo check` clean with no warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 1 commit 2026-08-12 02:01:38 +00:00
Sweep the snapshot commits recreation leaves behind
Build App (Preview) / compute-version (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 2m40s
Build App (Preview) / build-linux (pull_request) Successful in 5m37s
Build App (Preview) / build-windows (pull_request) Successful in 6m16s
Build App (Preview) / prune-previews (pull_request) Successful in 5s
5f990dd28b
Every recreation commits the container to triple-c-snapshot-{id}:latest
and moves that tag; the image it pointed at keeps its layers and loses
its name. Nothing deleted those, so they accumulate — measured on one
real host, 7 orphans holding 7.4 GB, three of them from a single day's
work.

`sweep_orphaned_snapshots` removes them, under two conditions that are
the whole safety argument. Untagged: every image the app depends on
carries a tag, so a project's live `:latest` and a migration's
`pre-migration-*` rollback pin cannot match the filter at all. And
labelled `triple-c.managed=true`, which `docker commit` copies from the
container onto the image — the user's own dangling images are not ours
to delete. Removal is unforced on top of that, so Docker refuses while
any container is still built from the image, including the stopped
containers of projects that are not running; those are counted and left
for the next sweep.

It runs after a recreation, which is when the orphan it just made
becomes removable, and after a migration is accepted, which is the
moment dropping the pin turns the pre-migration snapshot into an orphan.
Both detached: this is housekeeping, and a full disk beats a project
that will not start. Each sweep clears every orphan it finds, so
recreations that predate it are cleaned up too.

The label string is now a constant rather than four literals, and a test
pins both filter conditions in place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jknapp merged commit be37723c38 into main 2026-08-12 02:06:24 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#23