Using ASDF with Elixir and Phoenix

By Jake Morrison in DevOps on Tue 26 March 2019

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, use releases. Releases bundle the VM with the code, so we don't need to install Erlang on the prod machine at all. 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.1
nodejs 10.15.3

The asdf install command reads the file and installs the necessary versions on your system if necessary.

Install ASDF

These instructions assume that you are running macOS on your dev machine and Linux on your prod machine.

This script automates the process of installing ASDF on macOS. Following are step by step commands with explanation.

First, get the ASDF code:

git clone ~/.asdf --branch v0.7.1

Add commands to your shell startup scripts:

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

The above commands installed from git to be more consistent with the Linux side.

You can also install via Homebrew:

brew install asdf
echo -e '\n. $(brew --prefix asdf)/' >> ~/.bash_profile
echo -e '\n. $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash' >> ~/.bash_profile

After installing ASDF, 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:

# Install common ASDF plugin deps
brew install coreutils automake autoconf openssl libyaml readline libxslt libtool

# Install Erlang plugin deps
brew install 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

# Install Node.js plugin deps
brew install gpg

# Import node gpg keys
# This can be flaky, as it depends on network connections to the GPG key servers
# You may need to run it multiple times
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`
# 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
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 "==> Install ASDF plugin dependencies"

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"

if [ ! -d "$HOME/.asdf" ]; then
    echo "===> Installing ASDF"
    git clone "$HOME/.asdf" --branch v0.7.1

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

source "$HOME/.asdf/"

if [ ! -d "$ASDF_DIR/plugins/erlang" ]; then
    echo "===> Installing ASDF erlang plugin"
    asdf plugin-add erlang

if [ ! -d "$ASDF_DIR/plugins/elixir" ]; then
    echo "===> Installing ASDF elixir plugin"
    asdf plugin-add elixir

if [ ! -d "$ASDF_DIR/plugins/nodejs" ]; then
    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

Install build deps

Finally, install Erlang, Elixir, etc. You would generally put it in the "build" phase of your scripts so the .tool-versions file in git controls the versions.

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