Short answer
The Reach toolchain side — the wrapper script and your project's pinned Reach release — should own version selection, and your repository should own recording the resolved digests. The Docker runtime should be treated as a dumb executor: it runs whatever image reference it is handed and should never decide which version that is. If you let the runtime (or a developer's local image cache) influence which image runs, you have moved the reproducibility boundary to the least controllable place.
Tag vs digest: what to verify, not assume
Whether the ./reach wrapper resolves images by mutable tag or by digest is a property of the specific Reach release you installed, and it has changed across versions of similar toolchain wrappers, so treat it as unverified until you check. Two quick checks on one machine settle it:
# See which images the wrapper actually pulls/uses
REACH_DEBUG=1 ./reach compile --help 2>&1 | grep -i 'docker\|image'
# After a compile, inspect what is in the local cache
docker images --digests | grep -i reach
If the wrapper references tags like reachsh/reach:x.y.z, then "same release" does not guarantee "same bytes" — a re-published tag or a stale local cache on one laptop will silently diverge. If it references @sha256:... digests, the release pin is already content-addressed and your remaining risk is much smaller.
Recommended ownership split
- Your repo owns: the Reach release version, the connector selection (Algorand vs Ethereum-style — this changes codegen, so it must be pinned alongside the compiler, not assumed), and a lockfile of resolved digests.
- The wrapper owns: translating that release into image references. Let it do its job; only override image references if verification shows tag drift.
- Docker owns: nothing about versions. Rootless or rootful, proxied or direct, it just executes.
A practical pattern: after first setup on a clean machine, record digests into a checked-in file:
docker images --digests --format '{{.Repository}}@{{.Digest}}' \
| grep -i reach > reach-images.lock
Then add a CI step that fails if the resolved digests differ from the lockfile. That gives you drift detection without fighting the wrapper. Only if you observe actual drift should you override image references yourself (e.g., via the wrapper's environment-variable overrides, if your release exposes them) — overriding prematurely couples you to internals that change between releases.
Rootless Docker and proxies
Neither rootless Docker nor a corporate proxy changes image reproducibility — digests are digests regardless of how the bytes arrived. The risk is at the devnet networking layer, not the compiler layer:
- Rootless Docker uses user-mode networking (slirp4netns-style), so published ports, source IPs, and container-to-host reachability can differ from rootful Docker. If your devnet workflow assumes
host.docker.internal or specific localhost bindings, test that path explicitly on the rootless machine. - A proxied registry affects pull time and may require
HTTP(S)_PROXY in the Docker daemon config (not just your shell), but a transparent caching proxy that serves correct content does not alter digests — verify with docker inspect on the pulled image if you suspect rewriting.
Bottom line
Pin the Reach release plus connector in the repo, record resolved digests in a lockfile, and let Docker be a generic executor. Verify the wrapper's tag/digest behavior on your exact release before deciding whether you need to override image references — that one check determines how much of this you actually have to build.