Releases
View SourceNow that our application is ready, you may be wondering how we can package our application to run in production. After all, all of our code so far depends on Erlang and Elixir versions that are installed in your current system. To achieve this goal, Elixir provides releases.
A release is a self-contained directory that consists of your application code, all of its dependencies, plus the whole Erlang Virtual Machine (VM) and runtime. Once a release is assembled, it can be packaged and deployed to a target as long as the target runs on the same operating system (OS) distribution and version as the machine that assembled the release.
To get started, simply run mix release
while setting MIX_ENV=prod
:
$ MIX_ENV=prod mix release
Compiling 4 files (.ex)
Generated kv app
* assembling kv-0.1.0 on MIX_ENV=prod
* using config/runtime.exs to configure the release at runtime
Release created at _build/prod/rel/kv
# To start your system
_build/prod/rel/kv/bin/kv start
Once the release is running:
# To connect to it remotely
_build/prod/rel/kv/bin/kv remote
# To stop it gracefully (you may also send SIGINT/SIGTERM)
_build/prod/rel/kv/bin/kv stop
To list all commands:
_build/prod/rel/kv/bin/kv
Excellent! A release was assembled in _build/prod/rel/kv
. Everything you need to run your application is inside that directory. In particular, there is a bin/kv
file which is the entry point to your system. It supports multiple commands, such as:
bin/kv start
,bin/kv start_iex
,bin/kv restart
, andbin/kv stop
— for general management of the releasebin/kv rpc COMMAND
andbin/kv remote
— for running commands on the running system or to connect to the running systembin/kv eval COMMAND
— to start a fresh system that runs a single command and then shuts downbin/kv daemon
andbin/kv daemon_iex
— to start the system as a daemon on Unix-like systemsbin/kv install
— to install the system as a service on Windows machines
If you run bin/kv start_iex
inside the release directory, it will start the system using a short name (--sname
) equal to the release name, which in this case is kv
. The next step is to start two instances, on different ports and different names, as we did earlier on. But before we do this, let's talk a bit about the benefits of releases.
Why releases?
Releases allow developers to precompile and package all of their code and the runtime into a single unit. The benefits of releases are:
Code preloading. The VM has two mechanisms for loading code: interactive and embedded. By default, it runs in the interactive mode which dynamically loads modules when they are used for the first time. The first time your application calls
Enum.map/2
, the VM will find theEnum
module and load it. There's a downside. When you start a new server in production, it may need to load many other modules, causing the first requests to have an unusual spike in response time. Releases run in embedded mode, which loads all available modules upfront, guaranteeing your system is ready to handle requests after booting.Configuration and customization. Releases give developers fine grained control over system configuration and the VM flags used to start the system.
Self-contained. A release does not require the source code to be included in your production artifacts. All of the code is precompiled and packaged. Releases do not even require Erlang or Elixir on your servers, as they include the Erlang VM and its runtime by default. Furthermore, both Erlang and Elixir standard libraries are stripped to bring only the parts you are actually using.
Multiple releases. You can assemble different releases with different configuration per application or even with different applications altogether.
We have written extensive documentation on releases, so please check the official documentation for more information. For now, we will continue exploring some of the features outlined above.
Configuring releases
Releases also provide built-in hooks for configuring almost every need of the production system:
config/config.exs
— provides build-time application configuration, which is executed before our application compiles. This file often imports configuration files based on the environment, such asconfig/dev.exs
andconfig/prod.exs
.config/runtime.exs
— provides runtime application configuration. It is executed every time the release boots and is further extensible via config providers.rel/env.sh.eex
andrel/env.bat.eex
— template files that are copied into every release and executed on every command to set up environment variables, including ones specific to the VM, and the general environment.rel/vm.args.eex
— a template file that is copied into every release and provides static configuration of the Erlang Virtual Machine and other runtime flags.
In this case, we already have specified a config/runtime.exs
that deals with both PORT
and NODES
environment variables. Furthermore, while releases don't accept a --sname
parameter, they do allow us to set the name via the RELEASE_NODE
env var. Therefore, we can start two copies of the system by jumping into _build/prod/rel/kv
and typing this (remember to adjust @computer-name
to your actual computer name):
$ NODES="foo@computer-name,bar@computer-name" PORT=4040 RELEASE_NODE="foo" bin/kv start_iex
$ NODES="foo@computer-name,bar@computer-name" PORT=4041 RELEASE_NODE="bar" bin/kv start_iex
To verify it all worked out, you can type Node.list
in the IEx section and see if it returns the other node. If it doesn't, you can start diagnosing, first by comparing the node names within each iex>
prompt and calling Node.connect/1
directly. With applications running, you can telnet
into them as usual too.
While the above is enough to get started, you may want to perform advanced configuration based on the environment you are replying to. Releases provide scripts for that, which are great to automate based on host, network, or cloud settings.
Operating System scripts
Every release contains an environment file, named env.sh
on Unix-like systems and env.bat
on Windows machines, that executes before the Elixir system starts. In this file, you can execute any OS-level code, such as invoke other applications, set environment variables and so on. Some of those environment variables can even configure how the release itself runs.
For instance, releases run using short-names (--sname
). However, if you want to actually run a distributed key-value store in production, you will need multiple nodes and start the release with the --name
option. We can achieve this by setting the RELEASE_DISTRIBUTION
environment variable inside the env.sh
and env.bat
files. Mix already has a template for said files which we can customize, so let's ask Mix to copy them to our application:
$ mix release.init
* creating rel/vm.args.eex
* creating rel/remote.vm.args.eex
* creating rel/env.sh.eex
* creating rel/env.bat.eex
If you open up rel/env.sh.eex
, you will see:
#!/bin/sh
# # Sets and enables heart (recommended only in daemon mode)
# case $RELEASE_COMMAND in
# daemon*)
# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND"
# export HEART_COMMAND
# export ELIXIR_ERL_OPTIONS="-heart"
# ;;
# *)
# ;;
# esac
# # Set the release to load code on demand (interactive) instead of preloading (embedded).
# export RELEASE_MODE=interactive
# # Set the release to work across nodes.
# # RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none".
# export RELEASE_DISTRIBUTION=name
# export RELEASE_NODE=<%= @release.name %>
The steps necessary to work across nodes is already commented out as an example. You can enable full distribution by setting the RELEASE_DISTRIBUTION
variable to name
.
If you are on Windows, you will have to open up rel/env.bat.eex
, where you will find this:
@echo off
rem Set the release to load code on demand (interactive) instead of preloading (embedded).
rem set RELEASE_MODE=interactive
rem Set the release to work across nodes.
rem RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none".
rem set RELEASE_DISTRIBUTION=name
rem set RELEASE_NODE=<%= @release.name %>
Once again, set the RELEASE_DISTRIBUTION
variable to name
and you are good to go!
VM arguments
The rel/vm.args.eex
allows you to specify low-level flags that control how the Erlang VM and its runtime operate. You specify entries as if you were specifying arguments in the command line with code comments also supported. Here is the default generated file:
## Customize flags given to the VM: https://www.erlang.org/doc/man/erl.html
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
## Increase number of concurrent ports/sockets
##+Q 65536
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
You can see a complete list of VM arguments and flags in the Erlang documentation.
Summing up
Throughout the guide, we have built a very simple distributed key-value store as an opportunity to explore many constructs like generic servers, supervisors, tasks, agents, applications and more. Not only that, we have written tests for the whole application, got familiar with ExUnit, and learned how to use the Mix build tool to accomplish a wide range of tasks.
If you are looking for a distributed key-value store to use in production, you should definitely look into Riak, which also runs in the Erlang VM. In Riak, the buckets are replicated and stored across several nodes to avoid data loss.
Of course, Elixir can be used for much more than distributed key-value stores. Embedded systems, data-processing and data-ingestion, web applications, audio/video streaming systems, machine learning, and others are many of the different domains Elixir excels at. We hope this guide has prepared you to explore any of those domains or any future domain you may desire to bring Elixir into.
Happy coding!