There are many ways to make configuration available to your application, but none of them is so versatile and easy to use as direnv.
direnvis an extension of your shell, enabling you to switch between your contexts easily by loading and unloading environment variables depending on your location within the directory tree.
It does that by combining 2 items:
.envrcfile created by you, the user, that contains the environment variables that are specific to the current directory/context
- a shell hook that makes sure that the
direnvbinary is called before the current shell displays a prompt.
Why would I use direnv?
- it’s written in Go and compiled into a single binary which is fast enough to have no visible impact while using the shell
- it’s language agnostic and hooks at the shell level rather than process level as other solutions (dotenv or similar)
- supports most of the popular shells (bash, zsh, tcsh, fish, elvish)
- has a set of useful builtin utility functions which can be extended by the user
- it has no external dependencies which makes it highly portable
The installation topics are covered in the official page, but some of the usual ones are:
apt install direnv# mac - https://formulae.brew.sh/formula/direnv
brew install direnv# suse / opensuse
zypper in direnv# snap - https://snapcraft.io/direnv
snap install direnv
After installation, you need to make sure that your current shell loads the
direnv binary. Once again some of the most common examples are:
echo eval "$(direnv hook bash)" >> ~/.bashrc# zsh
echo eval "$(direnv hook zsh)" >> ~/.zshrc# fish
echo 'direnv hook fish | source' >> ~/.config/fish/config.fish
After that, simply run
source <rc-file> in order to load the changes.
At the time being the Windows support is pretty basic, but it should work under WSL.
Ok, now what …
Now let’s see
direnv in action.
Let’s create a test directory using
mkdir test-project , move inside the directory with
cd test-project and create an
echo 'export DIRENV_ENV=hello-from-direnv' > .envrc
You should see a message like this
direnv: error /test-project/.envrc is blocked. Run `direnv allow` to approve its content
You should allow the changes with
in order to tell direnv that you are aware of the changes that were made to .envrc file and you want to apply them.
This is a security measure that prevents unwanted runs of the .envrc file (when cloning and browsing projects or other type of content from remote sources)
Right now, there will be a
DIRENV_ENV environment available to your shell. You can check it’s presence with
echo $DIRENV_ENV. Now if you will navigate out of your current directory with
cd .. the environment variable will magically vanish, since
direnv will restore the original environment (echoing environment variable again will return an empty line).
Please note that the setup inside .envrc files is available in all subdirectories under the root of the file.
Testing direnv with Docker
If you wish to simply test everything in a safe environment before doing modifications to your local one, you can do that with the help of docker and a
Dockerfile. Open you favorite editor, paste
FROM alpine:3.13ENV EDITOR=viRUN apk add bash \
&& apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing direnv \
&& echo eval "$(direnv hook bash)" >> ~/.bashrc
and save the file under the name
Dockerfile. Now, open the location of the
Dockerfile in your shell and build the image:
docker build --tag direnv-demo .
and finally, run the docker container:
docker run --interactive --tty direnv-demo bash
Editing direnv files
direnv comes with a handy command to edit its files, namely
direnv edit . Its behavior is based on the
EDITOR environment variable, meaning that it will open the file with whatever is defined in the
EDITOR variable. Examples:
export EDITOR=idea # to open with Jetbrains IDEAorexport EDITOR="code --wait" # to open with vscode
You can also directly edit the file and allow the changes (with
direnv comes with some utility functions that can be called inside the .envrc file (more can be added by the user by modifying the
~/.config/direnv/direnvrc file). The complete list is present in the official documentation.
Some of the most used ones are:
PATH_add <argument>— this adds the path contained by argument to the PATH environment variable
watch_file <file>— this will reload the environment every time <file> changes
strict_env— this enables strict evaluation over the evaluated shell contents, meaning that it will halt execution if any command exits with a code different from 0, or if it encounters any unused variables
use <program> <version>— A semantic command dispatch intended for loading external dependencies into the environment. The list of out-the-box usages is presented here
layout <type>— A semantic dispatch used to describe common project layouts. Type can be go/julia/node/php/perl/python
dotenv/dotenv_if_exists <path>— loads thedotenv file specified by <path>, optionally checking it exists when used in second form
has <command>— returns true if the command exists, false otherwise
source_env/source_env_if_exists <path>— sources another .envrc file specified by <path>
A real world example
Let’s try to create an .envrc file that will create a node.js environment using nvm (Node Version Manager), will also export 2 environment variables, namely
LOG_LEVEL and will add the local
binfolder to our PATH since we have some utility functions there.
direnv does not support
nvm out of the box, we will need 2 additional steps:
- install nvm following the instructions given here
- create a user defined function in
~/.config/direnv/direnvrcwith these contents (this will make it available for usage in any
type nvm >/dev/null 2>&1 || . ~/.nvm/nvm.sh
nvm use "$NODE_VERSION" # you can use install instead of use if you want to dynamically install version if it not exists
Now, you will add these lines to
use nodeenv 15.5.1
direnv allow in your shell will yield something similar to
direnv: loading /test/.envrc
direnv: using nodeenv 15.5.1
Now using node v15.5.1 (npm v7.3.0)
direnv: export +LOG_LEVEL +PORT ~NVM_BIN ~NVM_INC ~PATH
echo $LOG_LEVEL $PORT will yield the values that we exported
Also any files having executable flag under the
bin folder should be available to your shell without specifying the whole path.
One last note
If you wish git to ignore the
direnv files you can do that:
- globally using
touch ~/.gitignore $ git config --global core.excludesFile ~/.gitignoreand using
echo -e ".envrc\n.direnv" >> ~/.gitignore
- locally by simply adding it to the .gitignore file
echo -e ".envrc\n.direnv" >> .gitignore
The .gitignore file should look similar to
... other content ....direnv
In the end I really hope that this will help you unclutter your local setups as it did it for me.