feat(deployments): per-project Docker image retention with nightly pruning - #172
feat(deployments): per-project Docker image retention with nightly pruning#172bherila wants to merge 9 commits into
Conversation
2aaa827 to
355127e
Compare
fc568db to
d488ba3
Compare
…y pruning Built deployment images (e.g. careowner-211:latest) were never pruned, causing unbounded disk growth on busy hosts. This adds configurable retention so old images are removed automatically each night. - Add `image_retention_hours` column to `projects` (nullable i32; NULL falls back to the 48-hour system default) - Migration: m20260629_000001_add_image_retention_hours - `DockerCleanupService`: add `remove_image` to the `DockerClient` trait and `prune_old_deployment_images` which queries each project, finds deployments whose images are older than the project's retention period, and removes them - `DockerCleanupService`: add `default_image_retention_hours` field (default 48) and `with_default_image_retention_hours` builder - Expose `image_retention_hours` in the project API via `UpdateProjectSettingsRequest` and `ProjectResponse`; validated to 1–8760 h Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015a1UirztsZmSgG5aJw89kG
… models The projects entity gained an image_retention_hours column; five test helper functions that construct projects::Model literals directly needed the new field added to keep compiling.
…est project models Three more test helpers construct projects::Model literals directly and needed the new field: temps-agents (executor.rs, config_service.rs) and temps-notifications (vulnerability_notifications.rs).
91e1232 to
dfc9978
Compare
Addresses review findings on the image retention pass.
Data-loss fixes:
- Never prune images Temps cannot rebuild. Uploaded tarballs
(temps-{slug}-{env}:upload-{ts}) and external registry pulls have no
source to rebuild from, so removing one permanently breaks rollback and
promotion. Matched on deployment provenance rather than tag text, since
the upload endpoint accepts a caller-supplied tag.
- Never prune the image an environment is currently serving
(environments.current_deployment_id), regardless of age.
- Never prune images whose containers live on a worker node; this pass
only talks to the local Docker daemon.
- Raise the default window from 48h to 336h (14 days). Rollback and
promotion hard-fail once an image is gone, so this is a rollback
window, not a cache TTL. A 48h default silently destroyed the rollback
history of any project that did not deploy over a long weekend.
- Abort the whole pass (rather than fail open) when the protection
queries error.
Scale:
- Select only (id, project_id, image_name, created_at) instead of full
deployment models joined to full project rows. The previous query
materialised deployment_config, context_vars, commit_json and metadata
for every deployment ever created.
- Batch removals through one Docker connection instead of one per image.
Operability:
- Add AppSettings.image_retention (enabled + default_hours) so operators
can change or disable the policy at runtime via the settings row, per
the no-env-var-config rule. Out-of-range values are clamped.
- Report removed vs retained counts separately; a run where every removal
was refused previously logged "nothing to remove".
- Audit image_retention_hours on project settings updates.
- Add the setting to the project settings UI and to
`temps projects settings` (--image-retention-hours /
--reset-image-retention), with a warning below 48h.
Also: redate the migration to 20260803 so it applies after the migrations
already merged, drop the stale 48h references from docs, add
skip_serializing_if to the double-Option PATCH field, and rename
needs_preview_update to needs_project_row_update.
Tests: 13 unit tests including protection-beats-expiry ordering, and a
Docker-backed test asserting a real daemon removes an unreferenced image
and refuses one a container still references.
|
@bherila I reviewed this and pushed the fixes to your branch ( The design was sound — AND-ing eligibility across every reference, restricting to local Data loss (the important one)
Also protected: whatever each environment is currently serving ( The protection queries now abort the pass on error rather than failing open. A skipped night costs disk; a wrong deletion costs someone their deployment. The 48h defaultSince rollback needs the image, retention is the rollback window. At 48h any project that didn't deploy over a long weekend silently lost the ability to roll back to anything. I moved the default to 336h (14 days). That's a judgement call on my side — easy to change, and it's now an operator setting rather than a constant. Unbounded query
Also in the push
Evidence
Migration against a real Postgres: The retention rule is only as good as the non-forced removal underneath it, and that lives in bollard, so there's now a Docker-backed test (skips gracefully, no Worth noting what that test caught: my first version tagged both images off a shared busybox base and the "in use" assertion failed — Two things I could not verify and am not claiming:
On the "what else should be pruned" question
I'd suggest doing those as a follow-up that restructures |
|
@bherila heads-up — Re-verified against a real Postgres:
The full check matrix was green on the previous head ( |
Summary
Built deployment images accumulated indefinitely because the existing nightly Docker cleanup only pruned dangling tags. This adds a per-project
image_retention_hourspolicy (NULL= 48-hour system default) and removes expired Temps-managed local images during the nightly cleanup pass.Behavior and safety
m20260629_000001_add_image_retention_hoursand exposes the setting through the project response andPATCH /projects/{id}/settings.nullclears the override and restores the system default, while an omitted field leaves it unchanged.temps-*tags, leaving registry/external image references alone.~0 MB freedreport because per-image reclaimed-byte accounting is not available from this Docker API call.Validation
cargo fmt --all -- --checkcargo check -p temps-deployments -p temps-projects --libtsc --noEmit/healthzand/readyzboth pass.This remains a draft until the full CI matrix is complete.