Using ASDF with Elixir and Phoenix

For simple deployments, we can install Erlang and Elixir from binary packages. Instead of using the packages that come with the OS, which are generally out of date, you should use the packages from Erlang Solutions.

One disadvantage of the OS packages is that we can only have one version installed at a time. If different projects have different versions, then we have a problem. Similarly, when we upgrade the Erlang or Elixir version, we should first test the code with the new version, moving a version through dev and test environments, then putting it into production. If anything goes wrong, we need to be able to roll back quickly. To support this, we need to precisely specify runtime versions and keep multiple versions installed so we can quickly switch between them.

This is mainly useful for dev and build environments. For production, we use releases. Releases bundle the VM with the code, so we don't need to install Erlang on the prod machine. We just install the release and it includes the matching VM that we tested with.

The ASDF version manager lets us manage multiple versions of Erlang, Elixir and Node.js. It is a language-independent equivalent to tools like Ruby's RVM or rbenv.

ASDF is safe to install on your machine beside the Erlang packaged with your OS. It won't conflict with anything else, that's its whole reason for existing. You can, however, also use it to install a default global version.

It uses the .tool-versions file in your project to automatically set the path to use specific versions. The file looks like this:

erlang 21.3
elixir 1.8.0
nodejs 10.15.3

The asdf install command reads the file and installs the necessary versions on your system if they don't exist.

Install ASDF

This post assumes that you are running macOS on your dev machine and Linux on your prod machine.

First, get the ASDF code:

git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.7.0

Add commands to your shell startup scripts:

echo -e '\n. $HOME/.asdf/asdf.sh' >> ~/.bash_profile
echo -e '\n. $HOME/.asdf/completions/asdf.bash' >> ~/.bash_profile

After installing, log out of your shell and log back in to activate the scripts.

See the ASDF docs for more details.

Next, install the ASDF plugins for Elixir and Phoenix:

asdf plugin-add erlang
asdf plugin-add elixir
asdf plugin-add nodejs

Install build dependencies

ASDF builds Erlang from source, so it needs to have some build tools and libraries installed. Other packages like Node.js have dependencies as well.

On macOS, first install Homebrew, then run:

# Erlang
brew install coreutils automake autoconf openssl libyaml readline libxslt libtool unixodbc wxmac

# Install Java. It's optional, but installing it avoids popup prompts.
# If you already have Java installed, you don't need to do this
brew cask install java

# Node.js
brew install gpg
bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring

See the ASDF Erlang docs for more options.

Install tools

Use ASDF to install the versions of Erlang, Elixir and Node.js specified in the .tool-versions file:

asdf install

You might need to run this twice.

Install Elixir libraries into the ASDF dir for the specific Elixir version you are running:

mix local.hex --if-missing --force
mix local.rebar --if-missing --force

# Install the Phoenix archive (optional), so that you can run e.g. `mix phx.new`
# mix archive.install hex phx_new 1.4.2

Confirm that it works by building the app the normal way:

mix deps.get
mix deps.compile
mix compile

You should be able to run the app locally with:

mix ecto.create

# Webpack (the new hotness)
(cd assets && npm install && node node_modules/webpack/bin/webpack.js --mode development)

# Brunch (old and busted)
(cd assets && npm install && node node_modules/brunch/bin/brunch build)
iex -S mix phx.server
open http://localhost:4000/

Install ASDF on Linux

Following are scripts to install ASDF in a build/CI environment:

Install on Ubuntu 18.04

First, set up the base system and utils:

echo "==> Initialize package manager and install basic utilities"

export DEBIAN_FRONTEND=noninteractive

echo "===> Updating package repos"
apt-get update -qq

echo "===> Installing locale $LANG"
LANG=C apt-get -qq install locales
locale-gen "$LANG"

echo "===> Updating system packages"
apt-get -qq upgrade

echo "===> Installing apt deps"
apt-get -qq install dialog apt-utils

echo "===> Installing utilities"
apt-get -qq install wget curl unzip make git

Next, install build deps:

echo "==> Install ASDF plugin dependencies"

echo "===> Installing ASDF common plugin deps"
apt-get -qq install automake autoconf libreadline-dev libncurses-dev libssl-dev \
    libyaml-dev libxslt-dev libffi-dev libtool unixodbc-dev

echo "===> Installing ASDF Erlang plugin deps"
apt-get -qq install build-essential libncurses5-dev libwxgtk3.0-dev libgl1-mesa-dev \
    libglu1-mesa-dev libpng-dev libssh-dev unixodbc-dev xsltproc fop

echo "===> Installing ASDF Node.js plugin deps"
apt-get -qq install dirmngr gpg

Install on CentOS 7

First, set up the base system repo and utils:

echo "==> Initialize package manager and install basic utilities"

echo "===> Installing EPEL repository"
wget --no-verbose -P /tmp https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum install -q -y /tmp/epel-release-latest-7.noarch.rpm

echo "===> Updating package repos"
yum update -y -q

echo "===> Updating system packages"
yum upgrade -y -q --enablerepo=epel

echo "===> Installing utilities"
yum install -y -q wget curl unzip make git

Next, install build deps:

echo "===> Installing common ASDF plugin deps"
yum install -y -q automake autoconf readline-devel ncurses-devel openssl-devel \
    libyaml-devel libxslt-devel libffi-devel libtool unixODBC-devel

echo "===> Installing ASDF Erlang plugin deps"
groupinstall -y 'Development Tools' 'C Development Tools and Libraries'
yum install -y -q wxGTK3-devel wxBase3 openssl-devel libxslt \
    java-1.8.0-openjdk-devel libiodbc unixODBC erlang-odbc

echo "===> Installing ASDF Node.js plugin deps"
yum install -y -q install gpg perl perl-Digest-SHA

Install ASDF

Finally, install ASDF, same for Ubuntu and CentOS:

echo "==> Install ASDF and plugins"

echo "===> Installing ASDF"
git clone https://github.com/asdf-vm/asdf.git "$HOME/.asdf" --branch v0.7
echo -e '\n. $HOME/.asdf/asdf.sh' >> "$HOME/.bashrc"
echo -e '\n. $HOME/.asdf/completions/asdf.bash' >> "$HOME/.bashrc"

source "$HOME/.asdf/asdf.sh"

echo "===> Installing ASDF erlang plugin"
asdf plugin-add erlang

echo "===> Installing ASDF elixir plugin"
asdf plugin-add elixir

echo "===> Installing ASDF nodejs plugin"
asdf plugin-add nodejs

echo "===> Importing Node.js release team OpenPGP keys to main keyring"
# This can be flaky
bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring

echo "===> Installing build deps with ASDF"
asdf install
# Run it again to make sure, as there have been issues with plugin return codes in the past
asdf install