A guide to manage your environment variables in a better way using direnv

Shivam Arora
8 min readJun 28, 2020

--

Unclutter your .bashrc and provide a per-project scoping to your environment variables.

In this post, I’m going to discuss about a tool which I just found a few days ago called direnv.

direnv acts as an extension to your shell and it provides a feature using which you can load and unload environment variables depending on your current project directory.

This allows you to manage project-specific environment variables and not clutter your ~/.profile , ~/.bashrc or ~/.zshrc files.

How direnv provides a per-project scoping?

To do that, it checks for the existence of an .envrc file in the current and parent directories.

If it exists, the contents of .envrc are loaded into your shell environment.

Features of direnv

direnv com es with a bunch of features:

  • It is fast enough to be unnoticeable on each prompt.
  • Language-agnostic, so you can use it to build a solution with any programming language.
  • Supports a variety of shells, the list comprises of bash , zsh , fish , tcsh , elvish .
  • Provides a set of utility functions
  • Ability to create your own custom utility functions.
  • No npm/other package dependency is required unlike dotenv.
  • Hooks directly into the shell — unlike dotenv which hooks into the process, direnv hooks into the shell. Thus, it is available to any process within a given shell session.

This section was all about an intro to direnv , how it works and it’s benefits.

In the next section, let’s talk about how we can install and set it up.

Installation

There are two steps to the installation of the direnv :

  1. Installing the system package.
  2. Hooking into the shell.

Installing the direnv system package

Hook into the shell

Now just restart your terminal or source your config file by calling source ~/.bashrc or source ~/.zshrc , whichever applicable.

That’s it for the setup and installation.

You can now use direnv in any project you like.

We are going to discuss the usage instructions in the next section.

Note: As you might have noticed there are no setup instructions for Windows.
The reason for this being
direnv doesn’t natively support Powershell/CMD on Windows.
But you can still use
direnv on Windows using Windows Subsystem for Linux (WSL).
I’ve written a blog post earlier about wsl , so you can get to know how to get started with wsl by checking out this blog post and then follow the instructions for Linux as discussed above.

Using direnv in your projects

To start using direnv , you just need an .envrc file in your project.

Let’s create a project folder named my-project .

mkdir my-project

Create an .envrc file in this folder with an environment variable called MY_NAME :

echo export MY_NAME=shivam > my-project/.envrc

Now, move into your project directory by calling:

cd my-project

You’ll see the following error:

direnv, by default, blocks itself to load the contents of .envrc file into your session as a security precaution when you’re creating .envrc for the first time or whenever you’ve modified the contents of .envrc file.

This security precaution protects you in cases when you clone a project or unzip a project archive, you’re at a risk of wiping up your entire hard drive if you cd into this project without checking the contents of .envrc .

You must trust the contents of .envrc of the project and to tell direnv that you trust those contents and to load the contents, you must run direnv allow .

Since this is our own project and we know what’s inside the .envrc, we can easily provide trust to the content by executing the command:

direnv allow

direnv will now load the contents into the shell, as you can see below:

To test out if it has loaded our environment variable MY_NAME that we just provided earlier, print it’s value by using:

echo $MY_NAME

You must see the output as:

Now, once you move out of this project directory, you’ll see that direnv automatically unloads the contents of .envrc and you won’t be able to access the environment variable outside of your project.

Note: The contents of .envrc are accessible even inside the subdirectories of your project. So, you can always access them if you’re inside anywhere in your project directory.

In this way, you can scope your environment variables to your project as direnv loads the environment variables only when you move inside the project directory and automatically unloads them when you move out.

This section was all about the usage and demo of how direnv works within your shell.

In the next section, I’m gonna discuss how we can modify the contents of .envrc file and how these changes reflect inside your shell environment.

Editing the contents of .envrc

In every project lifecycle, we need to provide additional environment variables or modify them as our project grows. In order to do that, you need to edit the contents of .envrc .

You can edit the contents of .envrc using any of your favorite text editors.

But keep in mind that, whenever you modify the contents, you need to allow direnv to trust the contents of .envrc using direnv allow .

However, you can also avoid this behavior and load the contents in realtime whenever you edit the contents of .envrc .

To achieve this, direnv provides a handy command:

direnv edit

This command opens up the default text editor of your OS and once you modify the contents, save and close the file, all those contents will be loaded automatically without the need to run direnv allow again.

You can even hook your favorite text editor with direnv edit , so it will open up the provided editor instead of the os default.

To hook your favorite text editor, you need to provide a value for the $EDITOR variable in your shell environment.

Hooking up VSCode with direnv:

So, if I want to hook direnv edit with VSCode, all I need to do is to add the following line to my ~/.bashrc or ~/.zshrc :

export EDITOR="code --wait"

The contents of my ~/.bashrc must now look like this:

You can now add or edit any variable by calling direnv edit and it will automatically load into my environment.

In the next section, I’m gonna discuss about how you can setup a global .gitignore for all of your .envrc files inside all your projects.

Setting up a global .gitignore

Since .envrc might contain sensitive information, it’s best to put this file under the .gitignore in every project.

However, rather than adding it to each project’s .gitignore file, you can setup a global gitignore file using the following command:

Now, you can add a single entry for .envrc in this global .gitignore_global file that we just configured and it will be ignored in all of your git repositories.

The contents of ~/.gitignore_global file can contain:

Now if you remember about the features of direnv that we discussed in the beginning of this post, I mentioned that direnv also provides a set of utility functions.

So in the last section of this post, let’s discuss about what are those utility functions.

direnv Utility Functions

direnv also provides you with a set of utility functions that are available in the context of .envrc file.

As an example, the PATH_add function can be used to expand the values of the $PATH variable.

So instead of adding a new path using export PATH=<my-new-path>:$PATH, you can write PATH_add my-new-path and it will be automatically added to your $PATH variable.

In the following example, I just added our project directory which we created earlier to the path by calling direnv edit and entering the following into the .envrc file:

And we can now test out the result if our current directory has been added to the $PATH variable:

Other Utility Functions:

direnv calls these utility functions as stdlib functions. There are other useful functions available such as:

  • source_env - this function loads up another .envrc by specifying a path or its filename.
  • path_add - works just like PATH_add but can be used to provide a path to another variable such as specifying the JAVA_HOME for a directory.
  • dot_env - loads up a .env file into the current environment without the need of installing any dependency. So, your project has less load of a directory.
  • layout node - adds $PWD/node_modules/.bin to the PATH environment variable. So you can directly call executable commands present inside your node_modules without the need to refer the path such as calling ./node_modules/bin/lerna bootstrap. You'll be able to directly call lerna bootstrap without the need to prepend the path.
  • use node <version> - loads the specified NodeJS version from a .node-version or .nvmrc file.
  • watch_file <path> [<path> ...] - adds one or more files to a watch list. direnv will reload your shell environment on the next prompt, if any of the provided file changes.

You can find a list of all supported stdlib functions at this documentation link.

Note: It is also possible to create your own extensions by creating a bash file at ~/.config/direnv/direnvrc or ~/.config/direnv/lib/*.sh. These files are loaded before your .envrc and thus allow you to make your own customized extensions to direnv.

This was all about how you can manage your environment variables in a better way and scope them individually to your projects.

Hope you liked it and it was helpful. 😀

--

--

Responses (5)