0014. Do not volume-mount the mise runtime directory¶
Status¶
Accepted (amends ADR 0002)
Context¶
Runtimes (Python, Ruby, Go) are managed by mise and baked into the image at
build time (Dockerfile Layer D), where the network is unrestricted. See
ADR 0002. Everything installed into those
runtimes is baked too: the Layer E pip packages (ansible, ansible-lint,
pre-commit, …) and the Layer F gems (chef-cli, cookstyle, …) live under
~/.local/share/mise/installs.
devcontainer.json used to mount a persistent named volume,
${project}-mise-data, over ~/.local/share/mise. A fresh named volume starts
empty and overlays the image directory, so on first start it shadowed
every baked runtime and the pip/gem toolchain installed into them.
entrypoint.sh (seed_caches) responded to the empty directory by running a
fatal mise install, which tried to re-download the identical baked
versions from the network — while the proxy/firewall were not yet up during
postCreateCommand, and against hosts (e.g. dl.google.com for Go) that the
egress allowlist deliberately excludes. The result: tunnel error: Connection
refused and an aborted setup. On arm64/musl the Ruby half was doubly doomed
(no prebuilt binary; ruby-build scripts were themselves shadowed by the empty
~/.cache volume).
At runtime the container enforces a strict egress policy, so re-downloading a runtime cannot work regardless.
Decision¶
Do not mount a named volume over ~/.local/share/mise. Remove the
${project}-mise-data volume from devcontainer.json (this repo's own and the
examples/ template).
- The baked runtimes and the pip/gem toolchain installed into them are always visible; nothing shadows them.
entrypoint.shno longer seeds the directory.mise installruns only ifmise ls --missingreports a genuinely missing tool (a project.mise.tomlpinning a version the image never baked), and any failure is non-fatal.- Runtime version overrides are unsupported. The egress policy blocks the
downloads a different version would need. To change a runtime version, bump the
image's
.mise.toml(Renovate tracks it) and rebuild.examples/.mise.tomldocuments this and no longer advertises override examples.
Consequences¶
Accepted:
- First-run setup can no longer break on the mise step; the default case does
zero network I/O for runtimes.
- No image growth. (An earlier iteration seeded an empty volume from a build-time
/opt/mise-seed snapshot; that cp -a duplicated the ~1.1 GB runtime tree,
roughly doubling the largest part of the image and exhausting the CI runner's
disk during the build. Removing the volume removes the need for a seed at all.)
- ~/.local/share/mise is no longer persisted across container recreations.
This loses nothing of value: the runtimes are baked (recreated identical on
every start), and runtime-written state there could not usefully persist
because runtime installs are blocked by the egress policy.
Rejected alternatives:
- Seed the empty volume from a build-time /opt/mise-seed copy. Works, but
duplicates ~1.1 GB into the image (slower pulls for every consumer) and broke
the CI build on no space left on device. Rejected in favor of removing the
volume entirely.
- Allowlist dl.google.com et al. and repair the ordering. Weakens the egress
posture and would still fail because the proxy isn't up during
postCreateCommand.
Migration note¶
Existing consuming projects: after update-project.sh syncs the new
devcontainer.json, the ${project}-mise-data volume is no longer mounted (the
old Docker volume, if any, is left orphaned and can be removed with
docker volume rm). No action is required for correctness.