Skip to content

feat(admin): automatic Docker cleanup on every node - #262

Merged
bobdivx merged 1 commit into
mainfrom
feat/docker-gc
Oct 21, 2026
Merged

bobdivx merged 1 commit into
mainfrom
feat/docker-gc

Conversation

@bobdivx

@bobdivx bobdivx commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Why

On 2026-10-21 the NAS Docker disk (/media/Docker, 477 GB btrfs) filled to 100%: about 425 GB of images and build cache from DevForge builds, and Docker stopped. The old host cron /etc/cron.d/devforge-disk-guard (pre-v2) had been failing every 10 minutes since August, because dcron rejects its root field and variable lines.

What

Scheduler (leader, in-process)

  • Defaults: enabled, daily at 03:00 Europe/Paris (computed in Europe/Paris whatever the host timezone is), 24 h retention, 3 images kept per project, disk alert at 85 %.
  • Above the threshold, an emergency cleanup runs on that node, at most once an hour. The emergency also clears all unused build cache.
  • Runs on every cluster node through the existing executor: local, or POST /internal/exec on workers. Only one cleanup runs at a time.

Scope

  • BuildKit cache: docker buildx prune --builder <b> --filter until=<ret> for each registered builder, plus buildctl prune --keep-duration <ret> inside each running buildx_buildkit_* container. The second covers builders that aren't registered for the user (ZimaOS).
  • Unused images older than the retention.
  • Old deployment images beyond the N kept per project. Each successful deploy now also tags df-<id>:r<ts>, so rollback images exist and can be counted.
  • Never deleted: volumes, containers (including stopped ones), any image used by a container, df-*:latest.
  • Never run: system prune, container prune, image rm -f. Without -f, Docker itself refuses to remove an image that is in use.

Admin page (/app/admin?tab=docker, plus a hub tile)

  • Tiles: automatic cleanup (one switch), "Nettoyer maintenant", history, log rotation, and one tile per node showing disk use.
  • Details and settings open in modals, sized for mobile.
  • "Nettoyer maintenant" first shows a read-only dry run (per node: images with their reason, reclaimable cache, protected items), then asks for a destructive confirmation.
  • History: space freed, image count, duration, disk % before/after, errors.

Logs

  • Optional --log-opt max-size/max-file for containers launched by DevForge. Off by default, and refused if a node's logging driver isn't json-file or local.
  • daemon.json is never edited; the modal shows the snippet to apply by hand.

Environment

  • Falls back to DOCKER_CONFIG=/tmp/devforge-docker-gc when ~/.docker is read-only.
  • The legacy /etc/cron.d/devforge-disk-guard is moved (not deleted) to /DATA/.devforge/state/ when the leader starts, if the host filesystem is visible. If DevForge runs in a container, the node modal says so instead.

API

  • GET/PUT /api/v1/admin/docker-gc
  • GET /api/v1/admin/docker-gc/nodes
  • POST /api/v1/admin/docker-gc/preview (dry run)
  • POST /api/v1/admin/docker-gc/run with {confirm:true}
  • GET /api/v1/admin/docker-gc/runs

All are admin-only.

Tests

  • docker_gc_tests.rs: retention, keep-N per project, images of running and stopped containers protected, :latest protected, unsafe refs skipped, scripts never touch volumes or containers (no -f), inventory / buildx ls / sizes / cleanup output parsing, schedule and cron validation, emergency cooldown, legacy cron moved rather than deleted.
  • docker.rs: --log-opt arguments.
  • lib.rs: the rollback tag.
  • Vitest: formatBytes.

Docs: docs/DOCKER_GC.md.

- Scheduler on the leader (default: daily 03:00 Europe/Paris), emergency
  cleanup when a node's Docker disk exceeds the alert threshold (85%).
- Build cache: buildx prune per builder + buildctl prune inside running
  buildx_buildkit_* containers, older than the retention (24h).
- Images: unused images older than the retention, old deployment images
  beyond the N kept per project (3). Never images used by a container
  (running or stopped), never df-*:latest, never volumes/containers,
  never image rm -f.
- Each successful deploy tags df-<id>:r<ts> so rollback images survive.
- Admin page: tiles (auto switch, clean now with dry-run + confirmation,
  history, log rotation, per-node disk), details in modals.
- Optional --log-opt rotation for DevForge-launched containers;
  daemon.json never edited (snippet shown).
- Retires the broken legacy /etc/cron.d/devforge-disk-guard when the
  host is visible; DOCKER_CONFIG fallback for read-only ~/.docker.
@bobdivx
bobdivx merged commit 1bf66ad into main Oct 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant