How to use a Different Application Version for Each Project

How to use different versions of globally installed applications like terraform, python, node, etc on a per project basis. This solution will work on any Mac or Linux distribution.

How to use a Different Application Version for Each Project

If you are like me and have many projects requiring different versions of global applications like terraform, python, node, etc., this solution is for you. Well, this solution is for you if you are on Mac or Linux.

Use Cases  

  • Upgrading to the latest application one project at a time.
  • Testing with different versions

Solution Overview

Dynamically update PATH environment variable for each project based on the current directory or parent directory. I use direnv which is a great tool that extends your shell to load and unload environment variables depending on the current director.  And it probably supports your shell of choice as long as it is  bash, zsh, fish, tcsh, or Elvish.

Tutorial Requirements

This how-to will use Mac OS, homebrew, and terraform; however, this solution will work for any platform or toolset.

  1. Mac OS X
  2. homebrew

Installing homebrew is easy.

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

3. direnv

You can easily install direnv with homebrew. To install direnv on other systems, see the direnv Installation Documentation.

brew install direnv

4. Hook direnv into your shell

To hook direnv into your shell following the instructions https://direnv.net/docs/hook.html for your specific shell.

Files

You can find the files created in this tutorial in my tips-tricks-workaround git repo.

Tutorial

  1. Install both terraform 12 and 13 using homebrew.  The output of the commands has been omitted:
brew install terraform       # installs current version, 13
brew install terraform@0.12

 Let's take a look at what was installed and where:

# ls -od /usr/local/opt/terraform* /usr/local/bin/terraform* | awk '{print $8,$9,$10}'

/usr/local/bin/terraform -> ../Cellar/terraform/0.13.0_1/bin/terraform
/usr/local/opt/terraform -> ../Cellar/terraform/0.13.0_1
/usr/local/opt/terraform@0.12 -> ../Cellar/terraform@0.12/0.12.29
/usr/local/opt/terraform@0.13 -> ../Cellar/terraform/0.13.0_1

2. Since we don't want to run the wrong version of terraform accidentally, we will remove /usr/local/bin/terraform with the brew unlink command:

# type terraform
terraform is /usr/local/bin/terraform

# brew unlink terraform
Unlinking /usr/local/Cellar/terraform/0.13.0_1... 1 symlinks removed

# type terraform
type: Could not find 'terraform'

Perfect, now we will not accidentally run terraform 13 on a terraform 12 state file and update it.

3. I will assume all your projects are under the same directory, such as Projects.   Here is the Project layout for this tutorial.  

/Projects/project-acme/
/Projects/project-beta/
/Projects/project-charlie/

4. Now, we will set up direnv to update your PATH based on each project's needs dynamically.  In your root project directory, Projects, add the following lines to the .envrc

export TF12_PATH=/usr/local/opt/terraform@0.12/bin
export TF13_PATH=/usr/local/opt/terraform/bin  # Terraform 13

5.  Create a .envrc file in each project and to add the needed version of terraform to the project.

source_up

PATH_add $TF13_PATH

The source_up is a direnv command that will walk up your directory tree to / looking for another .envrc and we need to do this first to load the variables set above.

The PATH_add is a direnv command to add to the front of your PATH environment variable easily.   Can you also do the shell way of PATH=$TF13_PATH:$PATH

5. That is it.  Now when you cd to different directories, your path will be updated to point to the correct version of terraform that you want. Let's see how that works.

~/Projects · (master±)
# type terraform
type: Could not find 'terraform'

~/Projects · (master±)
[1]# cd project-acme/

~/Projects/project-acme · (master±)
# type terraform
terraform is /usr/local/opt/terraform@0.13/bin/terraform

~/Projects/project-acme · (master±)
# cd ../project-beta/

~/Projects/project-beta · (master±)
# type terraform 
terraform is /usr/local/opt/terraform@0.12/bin/terraform

Alternate Configuration

Default value

Normally I do not recommend having a default value and instead specify it for each project because it makes future upgrades easier.  However, if you want to have a default value, you can update your root .envrc as below with a PATH_add  

export TF12_PATH=/usr/local/opt/terraform@0.12/bin
export TF13_PATH=/usr/local/opt/terraform@0.13/bin

# Optional: If you want a default terraform, add it here.
PATH_add $TF12_PATH

Use rc/profile

Instead of having a root level .envrc file, you can put the TF12_PATH and TF13_PATH environment variables in your account rc/profile files.  


bash ~/.bashrc
zsh  ~/.zshrc  
fish  ~/.config/fish/config.fish