Walkthrough
IMPORTANT: Before starting, please review the Terminology page to get familiar with the terms used in this guide.
This is a simple guide on how to use Distillery and releases in general. We will cover the following:
- Adding Distillery to your project
- Initializing Distillery
- Configuring your release
- Building your release
- Deploying your release
- Building an upgrade release
- Deploying an upgrade
Let’s get started!
Adding Distillery to your project
Just add the following to your deps list in mix.exs
:
defp deps do
[{:distillery, "~> MAJ.MIN", runtime: false}]
end
Then run mix do deps.get, compile
, and you’re ready to go!
Initializing Distillery
To set up your project for releases, Distillery will create a rel
directory in your
project root, all files related to releases will be in this directory.
Distillery also creates rel/config.exs
, which is the configuration file you will use
to configure Distillery and your releases. Depending on your project type, it will create
an appropriate default release configuration, along with two environments, :dev
and :prod
,
with typical configuration for those two environments. You can leave this configuration untouched,
or modify it as desired. We will look at this file and discuss its contents briefly, check out
Configuration for more information on this file
and available settings.
To initialize Distillery, just run mix release.init
.
NOTE: In this walkthrough, we’re making the assumption that this is a non-umbrella application, though there are no significant differences, see Umbrella Projects for more details on configuration specific to that setup.
Configuring your release
The default configuration file will look like the following (comments stripped):
use Mix.Releases.Config,
default_release: :default,
default_environment: :dev
environment :dev do
set dev_mode: true
set include_erts: false
end
environment :prod do
set include_erts: true
set include_src: false
end
release :myapp do
set version: current_version(:myapp)
end
Let’s talk about what these settings do from top to bottom. See the Configuration page mentioned above if you want to explore settings not covered in this guide.
use Mix.Releases.Config,
default_release: :default,
default_environment: :dev
This loads the configuration macros required by Distillery, and sets a few optional global settings:
default_release
, which specifies which release to build by default if one is not specified to mix release
,
and default_environment
, which specifies which environment to build by default if one is not specified to mix release
.
If default_release
is set to :default
, then the first release definition in the file will be used.
If default_environment
is set to :default
, then an “empty” environment will be used, and the only settings used
will be those set within a release
block.
environment :dev do
set dev_mode: true
set include_erts: false
end
This creates an environment called dev
, and configures a few settings which are optimal for quick iteration
during development. dev_mode: true
will symlink compiled BEAM files into the release directory, instead of copying them,
which ends up being significantly faster. include_erts: false
tells Distillery to not copy the Erlang Runtime System into
the release directory, and instead just use the system-installed ERTS.
environment :prod do
set include_erts: true
set include_src: false
end
This creates an environment called prod
, and configures a few settings which are optimal for a self-contained
production release package. include_erts: true
bundles the Erlang Runtime System so that the target system does not need
to have Erlang or Elixir installed, and include_src: false
will ensure that unnecessary source code files are not
included in the release package, reducing the final file size of the release.
release :myapp do
set version: current_version(:myapp)
end
This creates a new release definition, named myapp
, and sets the only required setting for a release definition,
version
to whatever the current version of the myapp
application is when mix release
is run, by using the
current_version/1
macro.
You must have at least one release definition in the config. You do not have to create any environments, though the example config does so for reference.
To recap, an environment is a group of settings which override those of a release when that environment is active, a
release is the specific configuration for a group of applications which will be packaged together. In a non-umbrella application,
it is usually the case that the release is named/versioned the same as the application being released. In umbrella applications,
this may or may not be true, depending on whether the umbrella is released per-app or as one. When building a release, the
release configuration is overlaid with the environment configuration, to form what is called a “profile”. In simpler terms,
if you have a release named myapp
, and an environment called dev
, the profile would be myapp:dev
.
Building your release
Now that we’ve configured our application we can build it! Just run mix release
. There are a few flags
to mix release
that you may be interested in using right now:
--verbose
, will log every action performed by Distillery, this is important when debugging issues or reporting them on the tracker.--name=<name>
to build a specific release from your config--env=<env>
to build using a specific environment from your config--profile=<name:env>
to build a specific combination of environment and release
See mix help release
for a description of all flags.
When you run mix release
, you should see something like the following:
==> Assembling release..
==> Building release myapp:0.1.0 using environment dev
==> You have set dev_mode to true, skipping archival phase
==> Release successfully built!
You can run it in one of the following ways:
Interactive: _build/dev/rel/myapp/bin/test console
Foreground: _build/dev/rel/myapp/bin/test foreground
Daemon: _build/dev/rel/myapp/bin/test start
As an example, building the release for the prod
environment looks like this:
==> Assembling release..
==> Building release myapp:0.1.0 using environment prod
==> Including ERTS 7.3 from /Users/paulschoenfelder/erlang/18.3/erts-7.3
==> Packaging release..
==> Release successfully built!
You can run it in one of the following ways:
Interactive: _build/dev/rel/myapp/bin/test console
Foreground: _build/dev/rel/myapp/bin/test foreground
Daemon: _build/dev/rel/myapp/bin/test start
At this point, you can run your release using one of the three commands listed in the output.
Deploying your release
IMPORTANT: When running mix release
for deployment to production, you should set MIX_ENV=prod
,
to avoid pulling in dev dependencies, and to make sure the compiled BEAMs are optimized.
NOTE: If you are deploying to a different OS or architecture than the build machine, you should
either set include_erts: false
or include_erts: "path/to/cross/compiled/erts"
. The former will
require that you have Erlang installed on the target machine, and the latter will require that you
have built/installed Erlang on the target platform, and copied the contents of the Erlang lib
directory somewhere on your build machine, then provided the path to that directory to include_erts
.
Let’s assume you’ve built your release with MIX_ENV=prod mix release --env=prod
and
are ready to deploy it to production. The artifact you will want to deploy is the release
tarball, which is located at _build/prod/rel/<name>/releases/<version>/<name>.tar.gz
. If you included
ERTS in the release, then you can simply copy this tarball to the target machine, extract it
with tar -xzf <name>.tar.gz
, and run it with bin/<name> start
. If you didn’t include ERTS,
make sure you have installed Erlang first, and then you can proceed with deployment.
Some alternative approaches to builds so that you can bundle ERTS easily:
- Build the release in a Docker container running the same OS version and CPU architecture, then copy the tarball out of the container for deployment to the target.
- Install Erlang in a Docker container running the same OS version and CPU architecture,
then copy the Erlang
lib
directory back to your build machine so that you can cross-compile your release. - Same as the above, but use Vagrant
Personally, I recommend the “build in Docker” approach, as it’s the easiest, and simple to setup.
The start
command of the boot script will automatically handle running your release as a daemon, but
if you would rather use upstart
or supervisord
or whatever, use the foreground
task instead.
Once started, you can connect a shell to the running release with bin/<name> remote_console
or
bin/<name> attach
, though I would avoid the latter unless you have good reason to use it, as exiting
with CTRL+C
will kill the running node, while doing so with remote_console
will simply exit the shell.
If started with start
, you can run stop
to shutdown the node. If started with foreground
, you can send
an interrupt with CTRL+C
or kill
to shut it down gracefully.
Building an upgrade release
NOTE: You do not have to use hot upgrades, you can simply do rolling restarts by running stop
, extracting
the new release tarball over the top of the old, and running start
to boot the release.
So you’ve made some changes and bumped the version of your app, and you want to perform a hot upgrade of the target system. This is pretty simple!
- Run
mix release --upgrade
to build an upgrade release
You will now have a new tarball in _build/prod/rel/<name>/releases/<upgrade_version>/
which you can use for the next step.
Deploying an upgrade
Important: This part is very straightforward with an important
caveat: Once you have deployed an upgrade, if you make a mistake in,
say version X (eg, version X has a bug that is detected in
production), you cannot “redeploy” a new release with the same version
number (X). If you try to do so, you will discover that your
application instance has marked version X as “old” and will refuse to
upgrade to that version number. If you find a mistake in your
application and you wish to continue with hot upgrades, rollback to
the previous version (X -1), cut a new release at version (X + 1), and
deploy it. At this time you will be able to upgrade twice in a row
X - 1 => X => X + 1
and have a clean production instance.
Now with that caveat explained, on to the easy stuff:
- Copy the new tarball to
<deployment_root>/releases/<upgrade_version>/<name>.tar.gz
, wheredeployment_root
is the directory where the old version of the release was deployed. Thereleases/<upgrade_version>
directory won’t exist, so make sure you create it first. - Run
bin/<name> upgrade "<upgrade_version>"
to install the new version of the release and hot upgrade the node.
That’s it! To downgrade to the previous version, run bin/<name> downgrade "<old_version>"
. Be aware
that you can only downgrade to the release you upgraded from. To downgrade to an arbitrarily old release,
you must downgrade to each version between the current version and the desired version first.