ASDF Version Manager

Programming languages release new versions. Development, CI, or production environments may have multiple versions of the same language installed.

A version manager program helps switch between versions for a machine or project.

Often, these programs are language-specific. Some examples are RVM, rbenv, and chruby for Ruby and nvm for Node. They may have different installation, configuration, and commands.

ASDF is a version manager with a plugin system to support different languages while maintaining a single installation, configuration, and command interface.

Install version manager

I use a laptop setup script to install or update macOS system prerequisites and then install or update ASDF:

if [ -d "$HOME/.asdf" ]; then
    cd "$HOME/.asdf"
    git pull origin master
  git clone "$HOME/.asdf"

Install languages

The script then installs or updates ASDF language plugins and languages:

asdf_plugin_update() {
  if ! asdf plugin-list | grep -Fq "$1"; then
    asdf plugin-add "$1" "$2"

  asdf plugin-update "$1"

asdf_plugin_update "go" ""
asdf install go 1.10

asdf_plugin_update "nodejs" ""
asdf install nodejs 8.9.0

asdf_plugin_update "python" ""
asdf install python 3.6.5

asdf_plugin_update "ruby" ""
asdf install ruby 2.4.2
asdf install ruby 2.5.1

Node has a special case: the NODEJS_CHECK_SIGNATURES=no environment variable setting skips checking downloads against OpenPGP signatures.


I manage my PATH in a single place: my ~/.zshrcshell profile. It contains:

# Prepend programming language binaries via ASDF shims

# mkdir .git/safe in trusted project to add binstubs

export -U PATH

ASDF shims are the second thing in my PATH, after ./bin for trusted working directories.

ASDF shims need to be so near the front of the PATH in order to take precedence over any other installations of the languages or binaries installed via the language:

% which go
% which node
% which python
% which ruby

Note, the ASDF README suggests instead:

. $HOME/.asdf/

This is an alternate approach for adding shims to PATH. It also adds shell completions.

Given infrequent direct use of asdf commands and excellent asdf help output, I haven't needed shell completions. I also prefer directly setting PATH for clarity and control.


The laptop setup script symlinks ASDF-related dotfiles from a Git repository to $HOME:

  cd dotfiles
  ln -sf "$PWD/asdf/asdfrc" "$HOME/.asdfrc"
  ln -sf "$PWD/asdf/tool-versions" "$HOME/.tool-versions"

The contents of ~/.tool-versions look like:

go 1.10
nodejs 8.9.0
python 3.6.5
ruby 2.5.1

ASDF uses these values as the "global" default language versions on the machine. These values can be overridden by individual projects with a ~/path/to/project/.tool-versions file.

The contents of ~/.asdfrc are:

legacy_version_file = yes

When set to yes, ASDF plugins will read "legacy" version filenames such as .ruby-version for Ruby and .nvmrc or .node-version for Node. This setting is useful when working on a project with teammates who are using version managers other than ASDF.


Once installed and configured, users mostly don't need to interact with ASDF; it will automatically read the needed versions for the working directory.

A few tips from asdf help include:

asdf current                  Display current version being used for all packages
asdf where <name> <version>   Display install path for an installed version
asdf which <name>             Display install path for current version
asdf local <name> <version>   Set the package local version
asdf global <name> <version>  Set the package global version
asdf list <name>              List installed versions of a package
asdf list-all <name>          List all versions of a package
asdf reshim <name> <version>  Recreate shims for version of a package

Those commands are often enough to debug most language-related problems. For example:

% webpack
zsh: command not found: webpack
% asdf reshim nodejs 8.9.0
% which webpack

Happy versioning!