How I Work: asdf

The Problem

It's likely you'll have to work on different projects which use different versions of tools. For example you might be working on a Terraform 0.11 project today and a Terraform 0.13 project tomorrow. To make things worse, that Terraform 0.11 project might need Terragrunt 0.21, while the Terraform 0.13 project might need Terragrunt 0.26 (contrived example, I know).

There are a few ways to handle this. You could version the tools themselves (e.g. terraform0.11). You can set up symlinks and have a script which flips them around. You can have a env file in each project you source. You could also use something like direnv.

There are tools like tfenv which set up Terraform on a per directory basis which can partially solve this. There are also tools like rbenv and pyenv which solve for Ruby and Python respectively. One problem is they all require separate set up and configuration, which can get old. Some don't even have automatic switching mechanisms, and require more tools.

A Solution: asdf

asdf is a meta version manager, it manages other version managers (tools like pyenv).

I like it because you install and configure it once and from then on you can use .tool-versions files in your projects to automatically switch (and optionally install) tool versions.

In my original example I might have a .tool-versions file in the second project which looks like this:

terraform 0.13.4
terragrunt 0.26.2

Running terraform or terragrunt in a folder (or child folder) which contains that .tool-versions file will use the correct versions. Running asdf install will ensure the correct versions are installed (if missing).

asdf works by generating stub scripts for all the executables in a plugin (what it calls a collection of versions for a program, python is handled via a pyenv plugin for example, so python3.9 is a wrapper). When you invoke the program the wrapper figures out which version you really wanted.

The biggest ask asdf has is you have to buy into it. To work properly it must sit near the beginning of your PATH so that its wrappers can handle the invocations and figure out what to call.

In my case my ~/.config/fish/ looks like this:

source ~/.asdf/
# my other PATH settings:
# set -x PATH $HOME/.local/bin $HOME/Dropbox/bin $PATH /usr/local/sbin

That's it, there's an equivalent for other shells. I find you have to make sure it sits ahead of Homebrew and you can't really mix it with any other env manager.


I just follow the official instructions for the git based install:

# mac homebrew step
$ brew install coreutils curl git

# install
$ git clone ~/.asdf --branch v0.8.0

# Configure using one of bash, fish or zsh

# bash
$ vim ~/.bash_profile
. $HOME/.asdf/
. $HOME/.asdf/completions/asdf.bash

# fish
$ vim ~/.config/fish/
source ~/.asdf/
mkdir -p ~/.config/fish/completions; and cp ~/.asdf/completions/ ~/.config/fish/completions

# zsh
$ vim ~/.zshrc
. $HOME/.asdf/
# the completions steps look framework dependent, good luck :)

Example: Installing Terraform and Python

# List available plugins
$ asdf plugin list all | head

# Add terraform and python plugins
$ asdf plugin add python
$ asdf plugin add terraform

# Install versions (if you have tab completion you can complete your way to victory)
$ asdf install python 3.9.0
$ asdf install python 2.7.18
$ asdf install terraform 0.13.5
$ asdf install terraform 0.11.14

# Activate these versions globally
# For python we use a feature which allows for multiple versions to be avaiable
$ asdf global python 3.9.0 2.7.18
$ asdf global terraform 0.13.5

# Check versions, these are your global versions
$ which python
$ python --version
Python 3.9.0
$ which terraform
$ terraform version
Terraform v0.13.5

# Set up specific versions in a project
$ cd my-project

$ asdf local python 2.7.18
$ asdf local terraform 0.11.14

# You can also just edit a .tool-versions file.
# There's one in ~/.tool-versions for global versions
$ cat .tool-versions
python 2.7.18
terraform 0.11.14

# Check versions again
$ terraform version
Terraform v0.11.14

$ python --version
Python 2.7.18