dev
← All ADRs

Package-level Image Build/Rebuild Lifecycle

ACCEPTEDEngine2026-10-07

Context

The lifecycle-tasks ADR defines the task vocabulary. It places two "build" concerns in tension:

  • augr:build means environment config aggregation (env.d/* -> .env.project), not Docker images.
  • augr:rebuild means force a Docker image rebuild (--no-cache) and recreate containers.

That ADR explicitly accepted the build (env) vs rebuild (image) naming as a trade-off ("non-obvious but consistent within the autograph domain"). In practice this surfaces several problems:

  • Lexical ambiguity. build and rebuild look like a base/re- pair but belong to different domains (config vs image). rebuild is not "build again".
  • Coupling. The global augr:rebuild ran a stack-wide down before a no-cache image build -- a surgical image concern tied to a full teardown.
  • Wrong compose project. The package task augr:pkg:agent:build built without --project-name, so Docker derived the project from the task directory and produced a differently-named image that the running stack (started by augr:up with an explicit project name) never adopted.
  • Shared image, two consumers. The agent image is used by both dev-agent and metalab, which run in separate compose projects. A single global rebuild risks both agents at once -- including the agent driving the work.

We want: an unambiguous vocabulary; the Docker image build integrated coherently into the lifecycle (not conflated with env build); and a safe way to roll a new shared image without taking the whole stack (and both agents) down simultaneously.

Options considered

Option A - Keep the global `augr:rebuild`, accept the ambiguity

Status quo. Document the build/rebuild distinction harder.

  • Advantage: Zero change. Symmetric with up/down/ps/logs at the augr: level.
  • Disadvantage: The ambiguity and the stack-wide-down coupling remain. The wrong-project package build stays broken. No safe shared-image staging.

Option B - Rename env `augr:build` to free `build` for Docker

Make build mean "docker image build" (compose-consistent) and rename the env step (e.g. augr:config).

  • Advantage: build would match the docker-compose meaning operators expect.
  • Disadvantage: Large churn -- augr:build is established across docs, ADRs, the bootstrap sequence, and muscle memory. High blast radius for a naming preference.

Option C - Image build/rebuild at the package level; drop the global rebuild (chosen)

Keep augr:build = env config. Keep the Docker image build integrated inside augr:up (idempotent, cached). Remove the global augr:rebuild. Expose image build/rebuild per package, where "build" already means "build image".

  • Advantage: Removes the ambiguity by elimination (no global rebuild); build is unambiguously env at the augr: level and image at the package level (compose-consistent). Enables a staged shared-image rollout. Fixes the project-name bug.
  • Disadvantage: Operators must learn that image rebuild moved to augr:pkg:agent:rebuild. Mitigated by a pointer note in the lifecycle Taskfile header.

Decision

Option C.

  1. augr:build stays env config aggregation (unchanged).

  2. Docker image build stays integrated in augr:up (idempotent, cached) as part of starting the stack. It runs in its own Task invocation, so it reads a freshly aggregated .env.project -- respecting the dotenv-at-parse-time constraint (config regeneration and image build must not share one invocation).

  3. The global augr:rebuild is removed. The only built image is the agent image, replaced by augr:pkg:agent:rebuild.

  4. Package-level image verbs. Three verbs, scoped by whether the package owns the image (holds the Dockerfile) or reuses a shared one:

    • build -- build the image, idempotent (layer cache; cheap when up to date). Image owner only.
    • rebuild -- force a from-scratch build (--no-cache), then recreate the package's container. The owner rebuilds its own image; a consumer of a shared image rebuilds that shared image through the owner's compose/build context, with a disclaimer in the task that the Dockerfile belongs to the owner.
    • reup -- recreate the package's container on the current image, no build. Owner and consumers. All MUST pass the engine's compose --project-name so images and containers align with what augr:up creates.
  5. Staged rollout for a shared image. When an image is shared across compose projects (the agent image serves dev-agent and metalab): (1) <owner>:rebuild rebuilds the image and recreates the owner's container (augr:pkg:agent:rebuild -> dev-agent); (2) validate; (3) <consumer>:reup recreates each other consumer on the proven image, no rebuild (augr:pkg:metalab:reup). Consumers keep running on the old image until their reup, so the agent driving the work is never torn down together with its target.

  6. Builds go through docker compose build (Compose V2 -> BuildKit by default), never a bare docker build. This keeps BuildKit features (notably build secrets) working on engines where BuildKit is not the default builder.

Convention (build / rebuild / reup)

Verb Semantics Exposed by
build Idempotent "ensure built" -- uses the cache, no-op if fresh image owner
rebuild Force "build again from scratch" (--no-cache) + recreate container owner and consumers
reup Recreate the container on the current image, no build owner and consumers

The image owner is the package holding the Dockerfile. A consumer reuses a shared image: its rebuild rebuilds that shared image through the owner's build context (with a disclaimer) and recreates only its own container; it has no build (the idempotent ensure-built is the owner's concern, also covered by augr:up).

Consequences

  • The build/rebuild ambiguity disappears at the augr: level (no global rebuild; build unambiguously = env). At the package level the pair is docker-compose-consistent.
  • Amends the lifecycle-tasks ADR: rebuild moves from augr: to augr:pkg:<name>: and gains a sibling reup; the verb matrix and package-task table gain build/rebuild/reup at the package level. That ADR's Status is annotated to point here. It also updates the safe-metalab-contribution-workflow ADR, whose shared-image and staged-rebuild references now use augr:pkg:agent:rebuild + augr:pkg:metalab:reup.
  • --project-name correctness becomes mandatory for package image tasks (fixes the previously mis-projected agent build).
  • proxy now builds its own image from the Ubuntu 26.04 archive to receive supported Squid security updates. It exposes rebuild, reup, and wait: rebuild forces a fresh package install, reup adopts the current image, and wait makes a startup failure visible before dependent agents start. metalab reuses the agent image: it exposes rebuild (rebuilds the shared image via pkg.agent, disclaimered) and reup, but not build.
  • reup makes image rollback correct: after retagging a backup image, augr:pkg:agent:reup recreates the container on the restored image without a rebuild -- whereas augr:up would rebuild from the current (possibly broken) sources and undo the restore.
  • Portability: forcing all image builds through docker compose build keeps the npm-registry build secret (a BuildKit --mount=type=secret) working on older engines where a bare docker build would not enable BuildKit by default.
  • Easier: safe shared-image refresh, clear vocabulary, portable builds. Harder: the augr:rebuild -> augr:pkg:agent:rebuild relearn (mitigated by the header pointer).

Follow-ups

  • Surface the build/rebuild convention in the package interface documentation and the seed/project docs.
  • Apply the convention to any future package that builds its own image.