Getting Started

Understanding Releases

A release describes the set of applications it needs to run, and how to run them. The central artifact of a release is a .rel file, which looks something like this:

{release,{"test","0.1.0"},
         {erts,"8.2"},
         [{kernel,"5.1.1"},
          {stdlib,"3.2"},
          {poison,"3.1.0"},
          {logger,"1.4.1"},
          {compiler,"7.0.3"},
          {elixir,"1.4.1"},
          {test,"0.1.0"},
          {iex,"1.4.1"},
          {sasl,"3.0.2"}]}.

The second element of the release tuple, is another tuple which describes the release name and version, the third element is a tuple which describes the version of ERTS (the Erlang Runtime System) which this release is targeting, and finally, a list of the applications and versions of those applications, which are required to run the release.

This .rel file is used to generate a boot script (which has a .script extension) and a compiled form of it (which has the .boot extension), which the Erlang Runtime System uses to boot the VM, very similar to how an OS is booted. This boot script looks like so (it is heavily truncated here for our demonstration):

%% script generated at {2017,2,22} {12,8,28}
{script,
    {"test","0.1.0"},
    [{preLoaded,
         [erl_prim_loader,erl_tracer,erlang,erts_code_purger,
          erts_dirty_process_code_checker,erts_internal,
          erts_literal_area_collector,init,otp_ring0,prim_eval,prim_file,
          prim_inet,prim_zip,zlib]},
     {progress,preloaded},
     {path,["$ROOT/lib/kernel-5.1.1/ebin","$ROOT/lib/stdlib-3.2/ebin"]},
     {primLoad,
         [error_handler,application,application_controller,application_master,
          code,code_server,erl_eval,erl_lint,erl_parse,error_logger,ets,file,
          filename,file_server,file_io_server,gen,gen_event,gen_server,heart,
          kernel,lists,proc_lib,supervisor]},
     {kernel_load_completed},
     {progress,kernel_load_completed},
     ..snip..
     {path,["$ROOT/lib/test-0.1.0/ebin"]},
     {primLoad,
         ['Elixir.Test,'Elixir.Test.Server', ...]},
     {kernelProcess,heart,{heart,start,[]}},
     {kernelProcess,error_logger,{error_logger,start_link,[]}},
     {kernelProcess,application_controller,
         {application_controller,start,
             [{application,kernel,
                  ..snip.. }]}},
     {progress,init_kernel_started},
     ..snip..
     {apply,
         {application,load,
             [{application,test,
                  [{description,"test"},
                   {vsn,"0.1.0"},
                   {id,[]},
                   {modules,
                       ['Elixir.Test','Elixir.Test.Server',
                        'Elixir.Test.ServerB','Elixir.Test.ServerC',
                        'Elixir.Test.Supervisor']},
                   {registered,[]},
                   {applications,[kernel,stdlib,poison,logger,elixir]},
                   {included_applications,[]},
                   {env,[]},
                   {maxT,infinity},
                   {maxP,infinity},
                   {mod,{'Elixir.Test',[]}}]}]}},
     ..snip..
     {progress,applications_loaded},
     {apply,{application,start_boot,[kernel,permanent]}},
     ..snip..
     {apply,{application,start_boot,[test,permanent]}},
     {apply,{c,erlangrc,[]}},
     {progress,started}]}.

As you can see, the boot script is full of low-level instructions which describe precisely how the VM, and the applications contained in the release, will be loaded and started. Every time you run erl or mix or iex, a boot script like the one above is used to boot the Erlang VM.

Given the description of a release (the .rel) and its boot script, a release is packaged by gathering all of the compiled .beam files required by the applications contained in the release, the target ERTS, and supporting files (sys.config for application configuration, vm.args for VM configuration, and a shell script used to set up the environment and run the release) - into a gzipped tarball for easy deployment.

Releases and Hot Upgrades

The use of releases enables one of the Erlang VM’s most powerful features - the ability to upgrade the system while it’s running. When generating upgrades, in addition to the .rel, .script, and .boot files, upgrades also require the definition of .appup files, which describe how to upgrade from one version of an application to the next. Each application which has changed must have a .appup defined, or it will not be upgraded. This file is high-level and relatively easy to write. It is used to generate a .relup file, which is a low-level description of how the entire release will be upgraded (or downgraded) from one version to another; similar to how the .script corresponds to the .rel file.

While Distillery has the ability to generate .appup files automatically for you, you should always take the time to inspect them and insure that they do the right thing for your application. It is also important that you understand how to leverage the system_code_change/code_change callbacks in your processes to transform an old version of your state to the new version. If this is not handled, your processes will be updated, but may fail in strange ways, due to missing struct fields you may have added in the new version, etc.

Installation/Setup

Simply add distillery to your dependencies, run mix deps.get and you are ready to start.

Within your project directory, you can then run mix release.init to setup your project with a rel directory containing a release configuration file (config.exs). Take a look at the output of mix help release.init to see how you can tweak this initial config file.

You can build a release with the mix release task. I strongly recommend reading the help output of the task before using it.

Configuration

The file you generated above, rel/config.exs contains the configuration of any releases you may wish to define, like so:

use Mix.Releases.Config,
  default_release: :foo,
  default_environment: Mix.env

environment :dev do
  set dev_mode: true 
  set include_erts: false
  set include_system_libs: false
  set cookie: :dev
end

environment :prod do
  set include_erts: true
  set include_system_libs: true
  set cookie: :prod
end

release :foo do
  set version: current_version(:foo)
end

Above, we’ve defined two “environments”, and one release. An environment is configuration specific to a particular target environment, for some this might mean different configs for test, staging, and prod; for others, it might mean different architectures or devices. It is flexible enough to support either, but out of the box it is set up to work with the current Mix environment, i.e. MIX_ENV=prod will use the :prod environment defined above.

The release we defined above is expected to match up to an actual :foo application, which should be the current Mix project. If we were working with an umbrella, or otherwise needed to deviate from this setup, we would define a release like so:

release :myapp do
  set version: "0.1.0"
  set applications: [:app_a, :app_b, some_dep: :load]
end

The key differences from above being that we’ve explicitly set the version (as current_version works by detecting the version of the given application), and explicitly set the applications to include in the release. As demonstrated above, you can also control the start type of an application in the release, such as loading, but not starting an application you need to dynamically configure before use at runtime. The applications setting is used to override any automatically determined information about a release, so in the case of the :foo release we originally defined, we could use the setting to override the start type for one of it’s dependencies if we so desire.

VM Configuration

Distillery will automatically generate a vm.args file for you, which configures the VM with a name and secure cookie, however there are times where you may want to provide your own, but still take advantage of metadata provided by Distillery. In this case, you would put set vm_args: "path/to/file" in your environment or release configuration, and define a file like the following at the path you provided:

## Node name
-name <%= release_name %>@127.0.0.1

## Node cookie, used for distribution
-setcookie ${NODE_COOKIE}

This file will be templated using the EEx template engine, and you can use any of the overlay variables described here.

Also shown above is the use of dynamic configuration. If REPLACE_OS_VARS=true is set in the runtime environment, a copy of vm.args will be made with ${NODE_COOKIE} replaced with the value of the NODE_COOKIE environment variable.

Application Configuration

Distillery will compile the configuration you define in config/config.exs (or whatever your config_path is set to) into a sys.config file, which is loaded by the VM at runtime to configure the applications in the release. A very important distinction to make here is that sys.config is not a dynamic file like config.exs, this means that if your config.exs file has a call to say System.get_env/1 in it, that call will be evaluated at compile-time, not run-time. If you need such configuration, either use the {:system, "ENV"} config options provided by your dependencies, and your application, or use dynamic configuration, as described above for vm.args. The dynamic configuration case has an additional limitation in Mix config files, because they can only be used within strings, making them unusable for configuration which requires integer values or other datastructures. In those situations, you have a couple of options:

  • Use vm.args with -<appname> <key> ${ENV_VAR} to configure those settings
  • Use Conform, or other configuration management libraries to help work around this limitation.

Next Steps

For more detail on building a release for a project, please see the Walkthrough doc. There is more detailed information on more advanced topics such as upgrades in other documents hosted here as well.

Please take the time to review the output of mix help release, as well as other release.* tasks, they cover more detail about usage at the command line, and what the various options mean.

Additionally, if you want to know what a certain configuration option does, you can find that information on the Configuration page.