{"id":19,"date":"2022-05-04T14:52:17","date_gmt":"2022-05-04T19:52:17","guid":{"rendered":"https:\/\/jcshort.net\/?p=19"},"modified":"2026-06-04T10:50:46","modified_gmt":"2026-06-04T15:50:46","slug":"context-specific-tool-management-with-direnv-asdf","status":"publish","type":"post","link":"https:\/\/jcshort.net\/?p=19","title":{"rendered":"Context-specific tool management with direnv"},"content":{"rendered":"\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<p class=\"wp-block-paragraph\">I&#8217;ve tried juggling virtualenvs by hand.  Who has the time to manually activate and deactivate when jumping between projects?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve tried language-specific version managers.  Sometimes they work.  Sometimes you have to wait double-digit seconds for re-shimming.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve tried using tool-specific wrappers like tfenv.  The cognitive burden increases exponentially with each one added to the pile.<\/p>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But none of them solve the entire problem.  Maybe this project needs a specific version of Python.  Maybe that one is stuck on a weird version of Terraform.  Yet another is using a deprecated Helm version, but the package manager only has the latest patch in that series.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/direnv.net\">Direnv<\/a> does a lot out of the box, but its strength is configuring projects to use an available version of a tool, it does not provide those versions itself.  Some languages have competent version manager tools that are worth using, but operational tools like terraform and kubectl generally lack version managers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s where <a href=\"https:\/\/asdf-vm.com\">asdf<\/a> comes in.  Not only is it a joy to type, when combined with direnv it&#8217;s actually <em>fast<\/em>.  Putting tool paths into a direnv include means no more waiting for slow re-shimming, running commands manually, or re-hashing your shell cache.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s as simple as:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code lang=\"bash\" class=\"hljs language-bash\">brew install direnv asdf\ndirenv hook\nasdf plugin add direnv\nasdf direnv setup --version system<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">You can then add <code>use asdf<\/code> to your <code>.envrc<\/code> file, and the plugin will modify PATH based on <code>.tool-versions<\/code>, e.g.:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code lang=\"bash\" class=\"hljs language-php language-bash\">$ <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"use asdf\"<\/span> &gt;&gt; project\/.envrc\n$ direnv allow project\n$ cat &lt;&lt; EOF &gt; project\/.tool-versions\nhelm <span class=\"hljs-number\">2.16<\/span><span class=\"hljs-number\">.3<\/span>\nkubectl <span class=\"hljs-number\">1.20<\/span><span class=\"hljs-number\">.15<\/span>\nterraform <span class=\"hljs-number\">0.14<\/span><span class=\"hljs-number\">.11<\/span>\nEOF<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Now, not only is the python virtualenv automatically activated and deactivated, so are those tools:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code lang=\"bash\" class=\"hljs language-javascript language-bash\">&gt; $ cd project\n<span class=\"hljs-attr\">direnv<\/span>: loading ~<span class=\"hljs-regexp\">\/project\/<\/span>.envrc\n<span class=\"hljs-attr\">direnv<\/span>: using asdf\n<span class=\"hljs-attr\">direnv<\/span>: loading ~<span class=\"hljs-regexp\">\/.cache\/<\/span>asdf-direnv\/env\/<span class=\"hljs-number\">2471675625<\/span><span class=\"hljs-number\">-973451084<\/span><span class=\"hljs-number\">-2702573808<\/span><span class=\"hljs-number\">-1004245725<\/span>\n<span class=\"hljs-attr\">direnv<\/span>: using asdf terraform <span class=\"hljs-number\">0.14<\/span><span class=\"hljs-number\">.11<\/span>\n<span class=\"hljs-attr\">direnv<\/span>: using asdf kubectl <span class=\"hljs-number\">1.20<\/span><span class=\"hljs-number\">.15<\/span>\n<span class=\"hljs-attr\">direnv<\/span>: using asdf helm <span class=\"hljs-number\">2.16<\/span><span class=\"hljs-number\">.3<\/span>\n<span class=\"hljs-attr\">direnv<\/span>: <span class=\"hljs-keyword\">export<\/span> ~PATH<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-large-font-size wp-block-paragraph\"><strong>But what about virtual environments?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">asdf is great for managing tool versions, but some tools have <em>baggage<\/em>.  Like python virtual environments.  For that, we&#8217;ll need something a bit more specialized: pyenv.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php \">$ brew install pyenv\n$ <span class=\"hljs-keyword\">echo<\/span> _PYENV_DIR=<span class=\"hljs-string\">\"$(pyenv prefix)\"<\/span> &gt;&gt; ~\/.zshrc\n$ source ~\/.zshrc<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">direnv &#8220;layout&#8221; mode places files (such as virtualenvs) into the <code>~\/.direnv<\/code> folder within the project:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript \">$ echo <span class=\"hljs-string\">\"layout pyenv 3.10.10\"<\/span> &gt;&gt; project\/.envrc\n$ direnv allow project\n$ cd project\n<span class=\"hljs-attr\">direnv<\/span>: loading ~<span class=\"hljs-regexp\">\/project\/<\/span>.envrc\n<span class=\"hljs-attr\">direnv<\/span>: <span class=\"hljs-keyword\">export<\/span> +PYENV_VERSION +VIRTUAL_ENV ~PATH\n\n$ which python\n\/redacted\/project\/.direnv\/python<span class=\"hljs-number\">-3.10<\/span><span class=\"hljs-number\">.10<\/span>\/bin\/python<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Python packages can be managed normally from here via <code>pip<\/code>.  Similar plugins exist for node, ruby, etc, and can be found in the <a href=\"https:\/\/direnv.net\/man\/direnv-stdlib.1.html\">direnv stdlib man page<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve tried juggling virtualenvs by hand. Who has the time to manually activate and deactivate when jumping between projects? I&#8217;ve tried language-specific version managers. Sometimes they work. Sometimes you have to wait double-digit seconds for re-shimming. I&#8217;ve tried using tool-specific wrappers like tfenv. The cognitive burden increases exponentially with each one added to the pile. [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_genesis_hide_title":false,"_genesis_hide_breadcrumbs":false,"_genesis_hide_singular_image":false,"_genesis_hide_footer_widgets":false,"_genesis_custom_body_class":"","_genesis_custom_post_class":"","_genesis_layout":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-19","post","type-post","status-publish","format-standard","category-tech","entry"],"featured_image_src":null,"featured_image_src_square":null,"author_info":{"display_name":"jshort","author_link":"https:\/\/jcshort.net\/?author=2"},"_links":{"self":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/posts\/19","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=19"}],"version-history":[{"count":0,"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/posts\/19\/revisions"}],"wp:attachment":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=19"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=19"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=19"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}