elm.lock versus a pinned Docker image for enforcing identical Elm compiler versions
0 reputation · 26 Feb 2025, 13:11 UTC
0 reputation · 26 Feb 2025, 13:11 UTC
In Elm 0.19.x, projects can achieve reproducible package resolution through elm.lock, which records exact package versions and revision hashes. However, the lock file does not pin the Elm compiler binary itself, so two machines with different installed compiler versions may still produce divergent build artifacts or error messages.
One documented approach is to standardize on a native installation procedure and document the required compiler version in the project README, relying on elm.lock for dependency determinism. The alternative is to require a fixed Docker image, such as elmlang/elm:0.19.1-slim, so that the compiler, runtime, and package tooling are identical across environments. The trade-off involves container maintenance overhead and the possibility that host system libraries affect native module compilation inside the container.
When the primary constraint is identical compiler behavior rather than dependency resolution, which approach better balances reproducibility with contributor friction?
elm.lock provide any mechanism to detect or reject a mismatch between the running compiler version and the version used to generate the lock file?32275 reputation · 26 Feb 2025, 17:30 UTC
When identical compiler behavior is the primary constraint, a container image pinned by digest is the stronger default. elm.lock is not a compiler lock; it only fixes dependency resolution. A documented native compiler version is lower friction, but it is a request, not an enforcement mechanism.
Confirmed behavior in Elm 0.19.x: elm.lock records the exact package versions and revision hashes resolved for your project. It does not record which compiler binary generated it, and Elm does not use the lock file to reject a version mismatch. Two machines with different installed compiler versions can read the same elm.lock and still produce different diagnostics or optimized output.
Likely guard: the elm-version field in elm.json plus an explicit CI check is the practical native defense. Treat any compiler-side rejection as version-sensitive and verify it against your exact patch release rather than assuming it is universal.
A container fixes the compiler binary, its runtime, and the package tooling in one artifact. Tags such as 0.19.1-slim are mutable, so pin by digest and update deliberately. The container also makes the toolchain explicit: contributors do not need to replicate a native install procedure.
To prove host-level dependencies are not altering the build:
ELM_HOME inside the container so package caches do not drift.--network none to confirm no registry access is required for the build.elm make --optimize artifact hashes.Any hash difference should be investigated before blaming Elm.
For pure Elm projects with no external bundlers, code generators, or native tools, elm.lock plus an exact compiler version can approach container determinism because host system libraries rarely affect elm make. It still depends on every contributor running the same compiler build. A CI check makes drift visible:
test "$(elm --version)" = "0.19.1"This fails on mismatch but does not prevent a local contributor from building with the wrong binary.
Do you have non-Elm build steps? If your pipeline includes a JS bundler, code generation, a native tool, or a platform-specific package, those live outside elm.lock and can break reproducibility. In that case, containerizing only the Elm compiler is insufficient; pin the entire toolchain in the same image or CI job.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.