0015. Inject the image version at scaffold time¶
Status¶
Accepted
Context¶
setup-project.sh scaffolds a consuming project by copying the examples/
templates verbatim. examples/devcontainer.json carries an exact image pin
(felipecoelho90/dev-container:X.Y.Z). Because the copy is byte-for-byte, every
scaffolded project inherited whatever tag happened to be in the template.
That tag only advanced when the weekly Renovate run fired after a release
published (the dev-container-image group). Between a release and that run —
and whenever Renovate missed — a freshly scaffolded project pointed at a stale
image. In practice the template had drifted as far back as 2.0.0 while the
repo shipped 2.3.x (see CHANGELOG.md).
Two fixes were considered:
- Floating major tag — set the template to
felipecoelho90/dev-container:2. Can never go stale and matches the recommended consumer pin documented indocs/reference/cicd.md,AGENTS.md, and ADR 0010. But it gives up an exact, Renovate-pinnable version by default. - Inject the exact version at scaffold time — substitute the current release
into the emitted
devcontainer.json.
Decision¶
Inject the exact release version at scaffold time (option 2).
- A shared helper,
scripts/lib-version.sh, resolves the version from the dev-container clone: the newestv*git tag, falling back to the top released entry inCHANGELOG.md. It is sourced by both scaffolding scripts so the logic cannot drift between them. setup-project.shsubstitutes the resolved version into theimageline as it writes.devcontainer/devcontainer.json. If resolution fails, it falls back to the template's literal pin (so it can never emit a broken tag).- The template keeps an exact pin as that fallback; Renovate's
dev-container-imagegroup keeps it current. It is not hand-bumped. - After scaffolding, the image tag is owned by the consumer's own Renovate.
Therefore
update-project.shmust never rewrite it: it renders the template for comparison with the consumer's existing tag substituted in, so a version-only difference is not treated as drift and the pin is never downgraded to the (possibly older) version in the dev-container clone. Only non-version template changes are synced.
Consequences¶
Accepted: - A newly scaffolded project pins the exact current release, with no dependence on Renovate's cadence and no staleness window. - One shared resolver; no duplicated version logic across the two scripts.
Trade-offs / known limits:
- Freshness is only as good as the user's dev-container clone: a months-old
checkout injects a months-old (but valid) exact pin. This is acceptable because
the consumer's own Renovate advances it afterward, and update-project.sh never
downgrades it.
- Chose an exact pin over the floating :2 tag deliberately: consumers get
reproducibility by default and can still opt into :2 manually.