Preparing the Elixir Development Environment
Muzhawir Amri

Muzhawir Amri @muzhawir

About: 🧙🏼⚗️ A Neophyte Elixir Alchemist

Location:
Indonesia
Joined:
Oct 2, 2018

Preparing the Elixir Development Environment

Publish Date: Jun 25
4 0

📜 You can read the Indonesian version here!

Table of Contents

What is the asdf version manager?

asdf banner

Erlang and Elixir can be installed directly using the guides on the Erlang/OTP download page and the official Elixir site. However, this approach has limitations in a development context: the system can only handle one runtime version at a time. If we want to use another version, a conflict will likely occur or the system will ask us to overwrite the version that is already installed.

To overcome this, we can use a version manager, a tool that lets us install and manage multiple runtime versions side by side on a single system. This approach allows us to support various projects with different runtime versions and switch between versions as needed.

A version manager also helps maintain consistency in a development team environment, because all members can use the same version and avoid compatibility issues.

In this article, we will use a version manager called asdf‑vm, or simply asdf.

Why Choose asdf?

In modern software projects, we often need more than one runtime, which also means we need several version managers to handle them. For example, to develop an Elixir‑based web application, we might use kerl for Erlang, kiex for Elixir, and nvm for Node.js. If each runtime is managed with a different tool, environment management becomes complicated and adds cognitive load.

asdf simplifies this by providing a single unified interface to manage various runtimes through a plugin system. With asdf, we can install and switch between versions of Erlang, Elixir, Node.js, and dozens of other runtimes using just one tool and a consistent command pattern.

Some key advantages of asdf:

  • Uniform interface, We don't need to learn different workflows for each version manager.
  • Extensive plugin support, including official plugins for Erlang and Elixir.
  • Cross‑project and team consistency, via the .tool-versions file that explicitly declares the version of each runtime.

There are currently nearly 100 plugins developed by the community. Because the plugins for Erlang and Elixir are developed officially, asdf is a highly recommended choice for Elixir project development.

Installing asdf

To install asdf, there are two main methods: first, using a package manager, and second, by downloading the precompiled binary from GitHub. Other methods like go install or building from source are also available, but we won’t cover those here. If you want to explore other approaches, you can refer to the official documentation on this page.

Let's start with the first method.

Using a Package Manager

This is the method recommended by the asdf maintainers. Currently, three package managers are officially supported: Homebrew (macOS), Zypper (openSUSE Linux), and Pacman (Arch Linux). Here are the installation commands:

# For Homebrew (macOS)
$ brew install asdf

# For Zypper (openSUSE Linux)
$ zypper install asdf

# For Pacman (Arch Linux via AUR)
$ git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si
Enter fullscreen mode Exit fullscreen mode

⚠️ The sudo command might be required depending on your system configuration.

If your operating system isn’t listed above, use the alternative method below.

Downloading the Precompiled Binary

This method requires git, so let's make sure it’s installed by running:

$ git --version
Enter fullscreen mode Exit fullscreen mode

If the version appears, git is already installed. If not, we’ll need to install it using our system's package manager:

# APT (Ubuntu Linux)
$ apt install git

# DNF (Fedora Linux)
$ dnf install git

# Pacman (Arch Linux)
$ pacman -S git

# Zypper (openSUSE Linux)
$ zypper install git

# Homebrew (macOS)
$ brew install coreutils git
Enter fullscreen mode Exit fullscreen mode

⚠️ The sudo command might be required depending on your system configuration.

Once git is ready, visit the asdf release page on GitHub and download the .tar.gz archive that matches our system’s architecture.

We should extract the archive into one of the directories listed in the $PATH environment variable. To see what directories are currently in $PATH, we can run:

$ echo $PATH
/home/your_username/.local/bin:/usr/local/bin:/usr/bin:/bin
Enter fullscreen mode Exit fullscreen mode

This output shows the directories separated by colons (:). We can choose one of these as the extraction target:

  • /home/your_username/.local/bin - a user-owned directory, great for non-root installations.
  • /usr/local/bin - commonly used for user-installed software.
  • /usr/bin and /bin - best avoided, as they’re part of the system core.

The most recommended directory is /home/your_username/.local/bin because it’s safe and doesn’t require administrative privileges.

Verifying the Binary Location

After extracting, let’s make sure our shell can detect asdf by running:

$ type -a asdf
asdf is /home/your_username/.local/bin/asdf
Enter fullscreen mode Exit fullscreen mode

If the command returns the binary path, then asdf is properly recognized. If not, the directory we extracted to is probably not in our $PATH.

Verifying the asdf Installation

Finally, let’s run:

$ asdf
Enter fullscreen mode Exit fullscreen mode

If the installation is successful, this will display the version and list of asdf subcommands.

asdf

Now asdf is ready for use. Next, we’ll install plugins for Erlang, Elixir, and Node.js.

Installing Plugins in asdf

asdf uses a plugin system to manage various runtimes. Before installing any runtime, we need to first add the corresponding plugin.

In this guide, we’ll add three plugins:

  1. Erlang - Since Elixir runs on the BEAM VM, Erlang is required for Elixir to work.
  2. Elixir - The main programming language we’ll be learning throughout this series.
  3. Node.js (optional) - Useful when managing frontend assets with the Phoenix Framework, such as JavaScript and CSS.

Erlang Plugin

The first step is to add the Erlang plugin.

But before doing that, we need to make sure that all necessary system dependencies are installed. The exact list depends on the operating system we’re using:

# APT (Ubuntu Linux 24.04)
$ apt install build-essential autoconf m4 libwxgtk3.2-dev libwxgtk-webview3.2-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libssh-dev unixodbc-dev xsltproc fop libxml2-utils libncurses-dev unzip

# DNF (Fedora Linux)
$ dnf install gcc g++ automake autoconf ncurses-devel wxGTK-devel wxBase openssl-devel libxslt fop unzip

# Pacman (Arch Linux)
$ pacman -S --needed base-devel ncurses glu mesa wxwidgets-gtk3 libpng libssh unixodbc libxslt fop unzip

# Zypper (openSUSE Linux)
$ zypper install make automake autoconf gcc-c++ ncurses-devel libssh-devel libopenssl-devel wxGTK3-3_2-devel fop libxml2-tools libxslt-tools unzip

# Homebrew (macOS)
$ brew install autoconf openssl@3 wxwidgets libxslt fop unzip
Enter fullscreen mode Exit fullscreen mode

⚠️ The sudo command may be required depending on your system configuration.

Once all dependencies are in place, we can add the Erlang plugin by running:

$ asdf plugin add erlang
Enter fullscreen mode Exit fullscreen mode

Then verify that the plugin was added successfully:

$ asdf plugin list
erlang
Enter fullscreen mode Exit fullscreen mode

If erlang appears in the list, the plugin has been added successfully.

Elixir Plugin

After adding the Erlang plugin, we can proceed to add the Elixir plugin:

$ asdf plugin add elixir
Enter fullscreen mode Exit fullscreen mode

Let’s verify the list of registered plugins again:

$ asdf plugin list
elixir
erlang
Enter fullscreen mode Exit fullscreen mode

Now both elixir and erlang are ready to use.

Node.js Plugin (Optional)

The Node.js plugin is optional, but very useful when developing Phoenix-based web applications, especially when dealing with frontend assets.

Just like Erlang, this plugin requires a few system dependencies to be installed beforehand:

# APT (Ubuntu/Debian)
$ apt install dirmngr gnupg curl gawk

# DNF (Fedora/CentOS/RHEL)
$ dnf install dirmngr gnupg2 curl gawk

# Pacman (Arch Linux)
$ pacman -S dirmngr gnupg curl gawk

# Zypper (openSUSE)
$ zypper install dirmngr gpg2 curl gawk

# Homebrew (macOS)
$ brew install dirmngr gpg curl gawk
Enter fullscreen mode Exit fullscreen mode

⚠️ The sudo command may be required depending on your system configuration.

Once the dependencies are installed, let’s add the Node.js plugin:

$ asdf plugin add nodejs
Enter fullscreen mode Exit fullscreen mode

Then verify:

$ asdf plugin list
elixir
erlang
nodejs
Enter fullscreen mode Exit fullscreen mode

Now all the essential plugins have been installed. In the next section, we’ll install the actual runtimes through asdf.

Installing Runtimes in asdf

In this section, we’ll install the runtimes required for Elixir development:

  • Erlang - version 27.3
  • Elixir - version 1.18.3-otp-27
  • Node.js - the latest version (at the time of writing: 23.10.0)

Since Elixir runs on the BEAM VM, we must ensure that the Elixir and Erlang versions we use are compatible.

Checking Elixir and Erlang Compatibility

Before we proceed, let’s confirm that the selected Elixir version is compatible with the Erlang version. This information can be found in the official Elixir documentation under the Compatibility and Deprecations section.

Here’s a snippet of the compatibility table:

Elixir version Supported Erlang/OTP versions
1.18 25 – 27
1.17 25 – 27
1.16 24 – 26

Since we’re using Elixir 1.18.3, Erlang 27.3 is a compatible and currently stable choice.

Checking Available Versions

Before installing, we can list available versions using:

$ asdf list all erlang

$ asdf list all elixir
Enter fullscreen mode Exit fullscreen mode

Make sure that:

  • Erlang 27.3 is listed in the first output.
  • Elixir 1.18.3-otp-27 is listed in the second.

For Node.js, we don’t need to choose a version manually since we’ll be installing the latest version.

Installing Erlang

Install Erlang 27.3:

$ asdf install erlang 27.3
Enter fullscreen mode Exit fullscreen mode

This process will download the source code and compile it locally. The installation time depends on the speed of our machine.

Set Erlang as the global default:

$ asdf set --home erlang 27.3
Enter fullscreen mode Exit fullscreen mode

This command writes erlang 27.3 to the .tool-versions file in our home directory, making it the default version system-wide.

Verify the installation by launching the Erlang shell:

$ erl
Enter fullscreen mode Exit fullscreen mode

If successful, we should see something like this:

erl Erlang

Installing Elixir

Install Elixir 1.18.3-otp-27:

$ asdf install elixir 1.18.3-otp-27
Enter fullscreen mode Exit fullscreen mode

Since Elixir is distributed as a precompiled binary, the installation process is relatively quick.

Set Elixir as the global default:

$ asdf set --home elixir 1.18.3-otp-27
Enter fullscreen mode Exit fullscreen mode

Verify the installation by launching the Elixir Interactive Shell (IEx):

$ iex
Enter fullscreen mode Exit fullscreen mode

If successful, we should see something like this:

IEx Elixir

Installing Node.js (Optional)

Install the latest version of Node.js:

$ asdf install nodejs latest
Enter fullscreen mode Exit fullscreen mode

Set it as the global default (replace 23.10.0 with the version that was installed):

$ asdf set --home nodejs 23.10.0
Enter fullscreen mode Exit fullscreen mode

Verify the installation:

$ node
Enter fullscreen mode Exit fullscreen mode

If successful, the Node.js REPL should appear, like this:

NodeJS REPL

At this point, all core runtimes have been successfully installed and configured. In the next section, we’ll explore how to manage global and local versions of each runtime as needed per project.

Managing Runtime Versions in asdf

After installing Erlang, Elixir, and Node.js, the next step is configuring which runtime versions to use. asdf supports two configuration scopes:

  1. Global versions - Applied system-wide
  2. Local versions - Applied to a specific project directory

Both approaches use the .tool-versions file to store version information.

Setting Global Versions

Global versions act as the default across the entire system unless overridden by a local configuration.

In fact, when we previously ran the asdf set --home commands, we already set the global versions. But if we ever want to update or reset them, here’s how:

$ asdf set --home erlang 27.3

$ asdf set --home elixir 1.18.3-otp-27

$ asdf set --home nodejs 23.10.0
Enter fullscreen mode Exit fullscreen mode

These commands will write entries into the .tool-versions file in the $HOME directory, making these runtimes globally available. The global versions are used when running commands like erl, iex, or node from any directory, unless that directory has its own local version configuration.

Setting Local Versions

Local versions are typically defined per project and stored in a .tool-versions file inside the project directory. While working within that directory (or any of its subdirectories), asdf will prioritize the local versions over the global ones.

To see how this works, let’s create a simple example project using mix:

$ mix new hello_world

$ cd hello_world
Enter fullscreen mode Exit fullscreen mode

We’ll configure this hello_world project to use different Erlang and Elixir versions than our global defaults:

$ asdf set erlang 26.0

$ asdf set elixir 1.17.0-otp-26
Enter fullscreen mode Exit fullscreen mode

These commands generate a .tool-versions file inside the project directory with contents like:

Local

To install all runtime versions listed in the local .tool-versions file, run:

$ asdf install
Enter fullscreen mode Exit fullscreen mode

This command downloads and installs any versions not yet available on the system:

asdf install

Once the installation is complete, we can verify that the local versions are active by running:

$ erl
Erlang/OTP 26 [erts-14.0] ...

$ iex
iex(1)> System.version()
"1.17.0"
Enter fullscreen mode Exit fullscreen mode

Erlang displays its version directly when the shell launches, while Elixir's version can be checked with the System.version() function inside iex.

asdf local version

The table below shows the difference between the global and local versions used in the hello_world project:

Global Version Local Version (Project hello_world)
Erlang: 27.3 Erlang: 26.0
Elixir: 1.18.3-otp-27 Elixir: 1.17.0-otp-26

This approach allows us to manage multiple projects with different runtime requirements in a clean and isolated way.

ElixirLS - Editor Extension for Elixir Development

🤩 Official Elixir Language Server

The Elixir team is currently working on an official Elixir Language Server. You can read more in this announcement blog post.

While waiting for a stable release, we’ll use ElixirLS, a community-driven project maintained by elixir-lsp.

ElixirLS is a Language Server Protocol (LSP)-based extension that significantly improves the Elixir development experience. It provides features such as code completion, module navigation, and interactive debugging, which make development more efficient and reduce the chance of errors. This is particularly useful because Elixir is a dynamically typed language, meaning type-related issues often only appear at runtime. ElixirLS adds helpful tooling like static analysis and type annotations that can help catch potential bugs early.

Key Features of ElixirLS

Here are the main features provided by ElixirLS:

  • Debugger - Supports step-through debugging. You can run your program line by line, inspect variable values, and trace the logic directly from the editor. This is especially useful when trying to debug issues that aren’t obvious with just IO.inspect/1.
  • Dialyzer Analysis - Dialyzer is a built-in static analysis tool from Erlang that detects type mismatches and possible bugs without running the code. ElixirLS runs Dialyzer incrementally in the background, so you get faster feedback while coding without needing to trigger the full analysis manually.
  • @spec Suggestions - Based on Dialyzer's type inference, ElixirLS suggests type annotations (@spec) for your functions. These annotations help improve documentation and make your code easier to understand.
  • Inline Errors & Warnings - Compilation errors and warnings, such as unused variables or incorrect argument counts, are shown directly in the editor. This removes the need to switch back and forth with the terminal.
  • Hover Docs - Hovering over a function or module name displays its documentation in a tooltip, allowing you to read docs without leaving the code.
  • Go-to-Definition & Find References - You can right-click a function or module name and choose Go to Definition to jump to where it is defined. The Find References feature shows all places where the function is used in your project.
  • Code Completion & Formatter - While typing, the editor suggests functions, variables, or module names that match your input. This speeds up development and reduces typos. The built-in formatter automatically formats your code to follow the official Elixir style guide.
  • Symbol Lookup - Lets you search for symbols such as functions, modules, or variables within the current file, across the project workspace, or in standard Elixir and Erlang libraries.

These features make ElixirLS an essential tool for improving code quality and developer productivity, especially when working on long-term or complex Elixir projects.

Installing ElixirLS in VS Code

If you're using Visual Studio Code, follow these steps to install ElixirLS. For other editors, see the IDE Plugins section in the ElixirLS README.

  1. Press Ctrl+Shift+X to open the Extensions sidebar.
  2. Search for “ElixirLS”.
  3. Select the extension called ElixirLS: Elixir support and debugger and click Install.

ElixirLS

After installation, ElixirLS will automatically build the PLT cache the first time it runs. PLT (Persistent Lookup Table) is a database used by Dialyzer to store type information from the standard library and project dependencies. This process might take a few minutes depending on your machine, but it only needs to be done once.

ElixirLS in Action

At this point, we have fully set up our Elixir development environment. We’ve installed asdf, configured the appropriate runtime versions, and integrated our editor with ElixirLS.

Comments 0 total

    Add comment