ADR 0032 — "Recreate pending" from the Compose configuration hash
Status: accepted.
Context
After editing .env or compose.yaml, nothing tells the administrator which
running services still use the previous configuration. The intuitive action,
Restart, is the wrong one: docker compose restart restarts the existing
container with its old environment, mounts and options. Only Up recreates a
container from the current configuration. The
runtime settings pattern relies on this
distinction to apply PHP/Bun settings from .env, so the panel should make it
visible instead of relying on the administrator to remember it.
Compose already records what it needs. Every container it creates carries a
com.docker.compose.config-hash label, the hash of that service's resolved
configuration at creation time. docker compose config --hash=<services>
prints the hash of the current configuration, and up recreates a container
when the two differ. ADR 0011 shows "Update ready" for the image side of the
same question; this ADR covers the configuration side.
Decision
- "Recreate pending" is derived locally, without changing anything, every
time a deployed stack page is rendered: for each service with at least one
container, the
com.docker.compose.config-hashlabel of its containers is compared with the hashcompose config --hashprints for that service now. A difference means Up would recreate that service with the current configuration. - Same project context as a mutation. The hash call runs with the stack's
explicit project name,
--project-directory,-ffile and--env-file(invariant 5), so the comparison reflects what Up would actually use, not a guess from the folder name. It sits behind theComposeProviderboundary as a read-only method with fixed arguments. - Compose 5.5.0 or later only. Before 5.5.0,
config --hashhashes the service without resolving its environment (env_file:is not merged intoenvironment), while containers are created from the resolved form: a stack usingenv_file:would always look pending. The provider readscompose version --shortat startup; below 5.5.0, or when the version cannot be parsed, the feature is unavailable and no marker is shown. The embedded Compose (pinned in theDockerfile) is the validated version; the lab suite runs against it.--no-resolve-envis never passed. - Services that reference another service's container are excluded. For
network_mode,ipcorpidset toservice:<name>, and forvolumes_from, Compose resolves the reference tocontainer:<id>before hashing at create time and inup's own comparison, whileconfig --hashhashes the declared form. The two never match, so the marker would stay after a successful Up. The panel detects these services from Docker, not from the raw configuration: a container whoseHostConfig.NetworkMode,IpcModeorPidModestarts withcontainer:, or whoseHostConfig.VolumesFromis not empty, gets no marker ("no information"). This also excludes a declaredcontainer:<name>that would hash consistently; the exclusion is deliberately conservative. Re-implementing Compose's reference resolution in the panel is rejected: it depends on Compose internals that changed between releases. - Only hashes leave the subprocess.
compose config --hashprints<service> <hash>lines. The panel parses only lines matching a known service name followed by 64 hex characters and discards everything else, including stderr, which may echo interpolated values. Raw resolved configuration is never read, logged or rendered (invariant 6). - One
container inspectgives both the hash and the references. The containers listed by the existingcompose ps --allcall are inspected once, through the existing read-onlyinspectContainersprovider method. From each record the panel reads onlyConfig.Labelsentriescom.docker.compose.serviceandcom.docker.compose.config-hash(accepted only as exactly 64 hex characters) and the fourHostConfigfields above. The labels string ofpsis not parsed: it joins values with commas that the values themselves may contain. With several replicas, any container whose hash differs makes the service pending. Stopped containers count: Up recreates them too. - Wording states the consequence, not a cause. The panel says that Up will recreate the service, not that "the configuration changed". If the containers were created by another Compose version that hashes differently, the panel's Compose would still recreate them on Up, so the statement stays true. The converse is not claimed: no marker does not mean Up will leave the service alone (image digest, network or volume mismatches and parent recreation are decided by Compose separately).
- UI. In the Services table, a pending service shows a "Recreate pending" marker next to the existing per-service Up form (ADR 0021), with a short note that Restart does not apply configuration changes. Server-rendered, no JavaScript, no new action.
- Any failure means "no information", never an invented state: Docker unavailable, timeout, truncated output, an unknown service in the output, a missing or malformed label, or a stack whose project context cannot be verified.
Not decided here
- Changes Compose does not hash. Inline
configs:/secrets:withcontent:do not change the service hash (verified with Compose 5.6), nor does editing a bind-mounted file on the host. The panel cannot detect those and does not claim to; the marker only covers what Compose itself would act on. - Network or volume definition changes that Compose handles separately from the service hash.
- Image changes: already covered by "Update ready" (ADR 0011). The two markers are shown independently.
- Any automatic Up after a save, or a combined "Save and apply" action: the save contract (ADR 0004) stays unchanged.
Consequences
- The safe path after editing
.envbecomes visible: save → read "Recreate pending" on the affected services → Up on those services. - Each deployed stack page render runs two more short read-only calls
(
config --hashandcontainer inspect), bounded by the existing process timeout and output caps. - The lab suite must cover, on an isolated daemon with the embedded Compose:
a changed
.envvalue used throughenvironment:makes only that service pending; the same throughenv_file:does too, and an unchangedenv_file:stack shows no marker; Restart leaves it pending; Up clears it; a service withnetwork_mode: service:<parent>orvolumes_fromgets no marker before or after Up; a changed inlineconfigs.contentdoes not mark it (documents the known gap); malformed or missing labels yield no marker. Unit tests cover the version gate (below 5.5.0, unparsable output).