fix: stop the four-hourly prune from deleting live containers - #39
Merged
Conversation
prune() ran two sweeps: an age-guarded `docker system prune -f --filter until=4h`, then `docker system prune --volumes -f` with no filter at all. The second removes every non-running container regardless of age, including one in `created` state. setup_cron() installs this to run every 4 hours at minute 0. When the unguarded sweep overlaps the ECS agent creating a task container, the container is deleted between `docker create` and `docker start` and the task fails with: CannotStartContainerError: Error response from daemon: No such container: <id> Replace it with `docker volume prune -f`, which reclaims the same anonymous volumes and cannot delete a container at all. Adds test/unit/prune.bats covering the age guard, the absence of any unfiltered system prune, and the diagnostics written.
|
hllvc
approved these changes
Aug 25, 2026
Member
Author
|
Correcting the attribution in the original description: the unfiltered sweep was introduced by |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
prune()runs two sweeps. The first is age-guarded; the second has no filter at all:An unfiltered
docker system pruneremoves every non-running container regardless of age — including one increatedstate that was made milliseconds ago.setup_cron()installs this to run every 4 hours at minute 0, so when the sweep overlaps the ECS agent creating a task container, the container is deleted betweendocker createanddocker start.How it got here
The guard was never meant to be dropped.
4e59eb0(2024-01-21, "fix volumes") split a single filtered call into two and did not carry the filter onto the volume half:Full timeline of the cron and the prune command:
bc52bdesetup_cron()introduced —doctoronly, no prune8411701prune()born + cron0 0 * * *(daily), filteruntil=<10 days ago>2836ef5clean_cron()added; schedule still daily12fe6a8until=24h;--volumesadded to the single filtered callbd887e3--volumesremoveddc530fa--volumesre-added — one call, still filtered, safe49819b90 0 * * *→0 */4 * * *; filter →until=4h4e59eb0919a298f54a4c1update_diagnostic; commented outsetup_cron/clean_cron; bug untouchedLive since 2024-01-21, ~31 months.
49819b9a week earlier had already moved the schedule from daily to four-hourly, so the two compounded: 6x the prune runs, one of them now unguarded.The fix
docker volume prunereclaims the same anonymous volumes and cannot delete a container at all, so this removes the capability rather than re-guarding it. Restoring--filter until=4hwould also work, but leaves the same footgun for the next edit — which is precisely how4e59eb0happened.Evidence — this fired in prod
Workflow run
/wfruns/fw325defo4rs(/orgs/rheinenergie), 2026-08-25 14:00 UTC, on private runnerprivate_ansible_iop:The runner host is UTC+2, so the
0 */4cron fired at 16:00 local = 14:00 UTC. The container was created 17 seconds later. Corroborating:/varat 24.3%, no kernel flags.StopTaskin CloudTrail — the removal was local, not API-driven.system prune, which evicts only dangling images.Test plan
Adds
test/unit/prune.bats(5 tests) and avolumecase to thedockermock.prune age-guards the container and image sweepprune never issues a system prune without an age filter— the regression guardprune reclaims volumes without a container-deleting sweepprune records the filter it actually applied to the guarded sweepprune records reclaimed space for both sweepsBoth new assertions confirmed RED before the fix, GREEN after. Local
make test-unitfailure count unchanged at the pre-existing baseline of 31 (api_call,patch_json,ecs_exec_deps— also failing onmain, macOS-tooling related); all green in CI.Follow-ups — not in this PR
setup_cronwas commented out inf54a4c1(2026-06-18), stopping new registrations from getting the cron — butclean_cronis commented out too, andmain.shhas no self-update. Every runner registered between 2024-01-21 and 2026-06-18 keeps running0 */4 * * * main.sh pruneagainst its on-disk copy, indefinitely. The affected runner registered 2025-11-04. Those hosts need the crontab entry actively removed.README.md:161says prune cleans "everything older than 10 days" — true of8411701in 2023, wrong since12fe6a8. Pre-existing, left alone here.system.docker.prune_filterreporteduntil=4hwhile the destructive sweep had none — actively misleading during this investigation. Accurate now as a side effect.