Files
werkator/docs/prs/2026-09-17-PR#77-what-nobody-names-any-more.md
61c6eeaa0d
werkator build succeeded after 02:00
werkator/deploy build succeeded after 00:07
feat(watcher): remove build environments no configuration names (#77)
Werkator imported every werkdock.rootfs into the store and never took
one out: a replaced rootfs left its image behind, one to two GiB each.
mih09 held two for weeks, and the disk trend read the steps as growth.

BuildEnvironmentPruner removes the werkator-buildenv-* images whose
source no repository's host configuration names (werkdock rmi) and the
download caches of URL sources no longer named. The watcher runs it
once per cycle for the whole instance, at most once an hour, never
while a build executes. Images anything else pulled are not touched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #77
2026-09-17 15:56:36 +02:00

6.0 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

Werkator imports every werkdock.rootfs into werkdock's image store and never takes one out again. A rootfs replaced by its successor — a new JDK, an added tool — leaves its image behind, one to two GiB each. On mih09 the store held two such images for weeks: the predecessor of werkjournal's rootfs, and the pre-ssh variant of the shared one whose archive had already been deleted. The System page's disk trend read the steps from imports and replacements as a disk growing half a gigabyte a day and advised an upgrade.

The same happens one level down: a URL rootfs is downloaded into .git/werkator/buildenv/<hash>/, and a changed URL leaves the old download there.

Non-Goals

  • Not touching what Werkator did not import. The store is shared by every process of the OS user; a postgres:16-alpine a Testcontainers test pulled is that test's business.
  • No age-based expiry. What is stale is what no configuration names; an image referenced by a repository stays however old it is.
  • No cleanup of sandbox homes (buildenv/home) or of archives an operator placed under buildenv/ by hand.

The Scenarios

Feature: build environments no configuration names are removed

Background

  • Werkator imports a werkdock.rootfs source as the image werkator-buildenv-<hash>, the hash over the source string.
  • werkdock.rootfs is pinned: a branch cannot name a source, so the host layers of the registered repositories are the complete set of references.

Scenario#77.01: Unreferenced images go, everything else stays

  • Given a store holding an image for the current rootfs, one for a replaced rootfs, and images pulled by something else
  • When the build environments are pruned with the current rootfs as the only reference
  • Then the replaced rootfs's image is removed with werkdock rmi
    • and the current one and the foreign images are not touched
Verified by

Scenario#77.02: A cleanup that fails fails alone

  • Given a store that cannot be listed, or an image rmi refuses
  • When the build environments are pruned
  • Then nothing is thrown; the failure is logged and the next round tries again
Verified by

Scenario#77.03: Download caches of unnamed sources go, the rest of buildenv/ stays

  • Given .git/werkator/buildenv/ holding the cache of a replaced source, the cache of the current one, the sandbox home, and an archive placed there by hand
  • When the download caches are pruned
  • Then only the replaced source's cache directory is removed
Verified by

Scenario#77.04: The watcher prunes once per cycle for the whole instance, at most hourly

  • Given a repository whose definitions name a rootfs and a werkdock binary
  • When two poll cycles run within an hour
  • Then the images are pruned once, through the configured binary, with every definition's rootfs as reference
    • and the repository's download caches are pruned once
Verified by

Scenario#77.05: Nothing is pruned while a build executes

So that a running sandbox never loses the image it is bound to.

Verified by

Scenario#77.06: A host without a sandbox is never asked for werkdock

So that a Docker or native host does not log a missing binary every hour.

Verified by

The Solution

BuildEnvironmentPruner does two things: pruneImages lists the store with werkdock images, keeps every werkator-buildenv-* name whose source is referenced, and removes the rest with werkdock rmi; pruneDownloadCaches removes the <hash>/ directories under a repository's buildenv/ whose hash no referenced source produces. Both derive the names from the source strings exactly as the runner does — sourceKey and imageName moved to WerkdockBuildRunner's companion so there is one place that knows the naming.

The watcher calls it after every repository is polled, once per cycle for the whole instance, because the store belongs to the OS user and not to a repository. At most once an hour — the store is asked through the CLI and changes when a configuration does — and never while a build executes, since a running sandbox binds its image. The referenced sources are every definition's werkdock.rootfs from every repository's primary configuration. A failure anywhere in the cleanup is logged and never reaches the poll cycle.

Additional Changes

  • On mih09 the two stale images (1.6 GiB) and werkjournal's replaced rootfs archive (207 MiB) were removed by hand on 2026-09-17, ahead of this PR.

Follow-up PRs

  • .konan, the Kotlin/Native toolchain cache in keluna's sandbox home, could share a directory like werkdock.gradleHome does (PR#76) once a second repository needs it.