Why asdf? Why direnv?

I end up explaining a pair of tools that I use all the time, so I am writing this down now so I can link to it later. I have worked in many environments where different services or projects were implemented in or relied on incompatible versions of different languages, dbs, etc. Some tools that help me deal with that are asdf and direnv. Asdf is a cli tool for managing runtime versions of software per project. Direnv is a cli tool for managing environment variables per project. Let’s start with asdf.

I use it for anything that might have different versions for different projects. I use it for development environment management when others might reach for docker, vagrant, or vms. Docker, vagrant, and vms are fine solutions to some problems, but I find them heavy for dev environment management. Our brave new world of changing silicon in 2020 and the gaps that those changes are exposing in our virtualization stories confirm that I have made the right choice. On Linux, docker rules, but on mac, it’s poky. Right now, on the M1, it doesn’t even run. Asdf is always quick and rarely surprises me. It just does its job.

I don’t see it as a replacement for brew, macports, pacman, apt, or whatever system package manager you prefer. Those will install a prescribed version of whatever you ask for across your whole system. When your package manager decides it’s time to upgrade, you upgrade. I actually prefer that the system do that for me for some things. Things like vim, openssl, or other programs that are likely not directly tied to my projects are great to hand off to other people to manage and coordinate. They’re not so great for things like python/node/ruby/elixir/erlang/postgres/redis/java/clojure/lisp that can be different from project to project. With asdf, you write a .tool-versions file at the root of your project containing something like this:

erlang 23.0.4
elixir 1.10.4
nodejs 12.18.3
postgres 13.1

Now that project will use those versions until you say otherwise. Do you need to work on a different project that requires a newer version of nodejs and an older version of postgres? No problem! Drop a .tool-versions file in said project with the versions that you need and get to work. Want some sensible system-wide defaults for when you don’t have a .tool-versions file? Drop a .tool-versions in your home directory! “Wait, isn’t this just like rbenv, nvm, etc.?” Yep, only for a lot more stuff; one runtime version manager to rule them all! “Couldn’t I do this with nix?” Now you’re starting to ask the right questions.

You could do this with nix. You could arguably do this better with nix. If you used nix, every project would be completely isolated from the other. It’s much purer. With asdf, if you specify nodejs 12.18.3 and do a global install of yarn in one project, every project using 12.8.3 will get yarn. That can be surprising, which is why I don’t do a lot of global installing. Is that a workaround? Yep. If that bothers you, nix will make you happier. Nix’s configuration is significantly more complicated, though, and I have had more issues with it in general. Asdf is the minimum effective dose. The config files are trivial to manage, and the cleanup when things inevitably shit the bed is as easy as running asfd uninstall nodejs <version_that_is_acting_up>. It just gets out of my way and lets me solve business problems instead of dealing with bespoke configs.

What about things like Rust, Go, and Java, whose multi-environment stories aren’t complete dumpster fires? Use what makes sense. I use asdf for Java. I don’t use it for Go and Rust. Why? Rust has largely solved this problem with rustup. Go isn’t quite as good, but version incompatibilities haven’t been a big issue for me.

What are the dependencies? Should I use this for CI/Prod deployments? The dependencies for asdf are reasonably light; however, you will need to install dev tools, jq, and some other things that are likely not things that you’d want to deploy to a production system. I probably wouldn’t use it to install and set up production or CI systems. It slays for dev systems, though. I have used it on Ubuntu, and I currently use it on Manjaro and MacOS.

What about direnv? Direnv is somewhat boring. You install it and then drop .envrc files in directories with environment variables that you want to use. Every time you cd to that directory, those environment variables spring into existence. When you leave the directory, they disappear. I don’t ever check my .envrc files into source. I usually create an example .env file to provide some direction to developers new to my projects. Once they are in place, I basically don’t think about them again. Again, it’s the minimum effective dose, and it just works.


Denver, USA
Email me

Jason Legler is a software developer living in Denver, CO with his wife, daughter, and lots of creatures. He enjoys building things on AWS. He likes learning programming languages, even though he rarely gets to use them. He misses Joe Armstrong. He builds amazing things for Stedi.