📜 You can read the Indonesian version here!
Table of Contents
-
What is the
asdf
version manager? -
Installing
asdf
-
Installing Plugins in
asdf
-
Installing Runtimes in
asdf
-
Managing Runtime Versions in
asdf
- ElixirLS - Editor Extension for Elixir Development
What is the asdf
version manager?
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
⚠️ 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
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
⚠️ 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
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
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
If the installation is successful, this will display the version and list of asdf
subcommands.
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:
- Erlang - Since Elixir runs on the BEAM VM, Erlang is required for Elixir to work.
- Elixir - The main programming language we’ll be learning throughout this series.
- 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
⚠️ 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
Then verify that the plugin was added successfully:
$ asdf plugin list
erlang
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
Let’s verify the list of registered plugins again:
$ asdf plugin list
elixir
erlang
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
⚠️ 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
Then verify:
$ asdf plugin list
elixir
erlang
nodejs
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
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
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
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
If successful, we should see something like this:
Installing Elixir
Install Elixir 1.18.3-otp-27:
$ asdf install elixir 1.18.3-otp-27
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
Verify the installation by launching the Elixir Interactive Shell (IEx):
$ iex
If successful, we should see something like this:
Installing Node.js (Optional)
Install the latest version of Node.js:
$ asdf install nodejs latest
Set it as the global default (replace 23.10.0
with the version that was installed):
$ asdf set --home nodejs 23.10.0
Verify the installation:
$ node
If successful, the Node.js REPL should appear, like this:
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:
- Global versions - Applied system-wide
- 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
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
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
These commands generate a .tool-versions
file inside the project directory with contents like:
To install all runtime versions listed in the local .tool-versions
file, run:
$ asdf install
This command downloads and installs any versions not yet available on the system:
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"
Erlang displays its version directly when the shell launches, while Elixir's version can be checked with the System.version()
function inside iex
.
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.
- Press
Ctrl+Shift+X
to open the Extensions sidebar. - Search for “ElixirLS”.
- Select the extension called ElixirLS: Elixir support and debugger and click Install.
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.
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.