Portfolio · Notes · Dotfiles

Search everything

Search case studies, engineering notes, and Dotfiles documentation.

    all notes

    Pinning tool versions with mise, and what not to pin

    One file pins Terraform for everyone who clones the repository; the version manager matters less than the decision of which tools deserve a pin at all.

    Every contributor and every CI job should run the same Terraform, and the way to get that is a version file in the repository, read by a tool that installs what it names. I use mise, which reads asdf’s .tool-versions as well as its own mise.toml, so a repository that pinned versions for asdf keeps working. The setup is three commands. The decision worth writing down is which tools to pin at all.

    The version file

    One line per tool, either in asdf’s format:

    .tool-versions
    terraform 1.9.7

    or in mise’s own:

    mise.toml
    [tools]
    terraform = "1.9.7"

    mise install reads whichever is present and installs what it names, and the shell then resolves the pinned binary:

    mise install
    # mise [email protected] ✓ installed
    which terraform
    # ~/.local/share/mise/installs/terraform/1.9.7/bin/terraform
    terraform version
    # Terraform v1.9.7

    That is the entire setup for a contributor: clone, mise install, done. CI does the same, so a pipeline and a laptop disagree about a tool version only when the file changed between them.

    One directory, another version

    In a monorepo one stack may still need an older Terraform than the rest:

    terraform {
    required_version = "1.9.4"
    }

    Two commands cover it. mise use [email protected], run in that directory, records the version in that directory’s configuration, and it applies whenever the shell is inside it. For a one-off run that should not leave anything behind:

    Terminal window
    mise x [email protected] -- terraform plan

    mise x installs the version if needed, runs the command with it on the path, and changes nothing else.

    What not to pin

    Pinning is not free. Every pinned tool is a stream of update pull requests from Renovate or Dependabot, and every merged one makes everyone run mise install again, so a pin has to buy something. Two examples of tools I leave out:

    The AWS CLI. Its minor releases almost never break anything a pipeline depends on, so a pin trades daily update noise for a guarantee nobody needed. Let it float.

    Python, in a repository that is not a Python project. The system version is enough for a helper script, and a project that is Python has a Python-native tool (uv, today) managing interpreters and dependencies together; a second manager for the same interpreter helps nobody.

    The tools that earn a pin are the ones whose version changes behaviour a reviewer cannot see in the diff: Terraform, whose state format and plan output depend on it; linters and formatters, whose rules do; anything that generates code that gets committed.

    Limitations

    mise use changes what terraform resolves to only in a shell where mise is activated (mise activate in the shell’s startup file). Without activation, mise x is the reliable form, and a script that assumes activation fails quietly on a machine without it.

    Tools outside mise’s core set are installed through asdf plugins, so the quality of the install is the quality of that plugin: a plugin that builds from source turns mise install into a compile.

    Two version files in one repository, .tool-versions for asdf users and mise.toml for mise, drift the moment one is edited without the other. Pick one; mise reads both, so the choice can be made for the asdf holdouts.

    Comments