Advantages of using Distillery/Exrm to run your phoenix server over plain mix phoenix.server?

Are there any advantages to building your releases with Distillery/Exrm and running them on the server versus MIX_ENV=prod mix phoenix.server?


With distillery/exrm you are building project into one small package which:

  • it’s already compiled, you don’t need to do it on each server
  • should be easier to distribute (i.e. less space, etc)
  • wraps the mix command with init.d script, which should handle signals better (as far as I remember), which is useful i.e.: for dockerizing the app

yeah but don’t you lose the hot code reloading ability then? This is somewhat a useful feature to patch production apps without downtimes

1 Like

That’s actually another thing that mentioned projects help achieve, as they prepare the releases to upgrade the code without restart, i.e. exrm docs - although if you use docker you don’t really use it.


Is there any difference in performance? For a single instance deploy I am leaning more towards a simple MIX_ENV=prod mix phoenix.server. Having mix on the server has its own advantages:

  1. Being able to run database migrations / seed without any extra steps
  2. Being able to load configuration directly from prod.secret.exs without the need to make it available when the release is built
  3. Much simpler deployment strategy

Would love to hear inputs from @bitwalker and @chrismccord too :slight_smile:

I would strongly oppose to the last point.

To me a single tarball you transfer to server, untar and run is a simpler solution than installing erlang, installing elixir, cloning repo, compiling code and running application.


I guess my Rails baggage is showing through that statement :slight_smile:

MIX_ENV=prod mix phoenix.server works quite well with tools like capistrano, mina and the lot. Moreover the setting up erlang and elixir is just a one time thing.

Exrm and Distillery packages your app as an OTP release, bundling ERTS (Erlang Run-Time System) with it so you have no Erlang and Elixir dependencies on your server. Just untar, run, and you’re up. Hot upgrades are also supported because it still is an Erlang app (but I don’t use it since I use docker, falling back to rolling upgrade).

And to address this, I’m using the Twelve-Factor App’s approach on config, basically by storing my secrets as env variables on my server. Distillery allows using env variables for runtime configuration with the REPLACE_OS_VARS=true option (docs).


Some advantages of OTP releases would be:

  • You don’t need to have compile-time artefacts on the prod server (source code, non-minified js, tests, etc).
  • You don’t need to have Erlang/Elixir/node and other compile-time deps installed on the prod server.
  • You can easily deploy new version of your system which uses different Erlang/Elixir.
  • You can easily run side-by-side different applications which use different Erlang/Elixir.
  • The helper shell script simplifies some tasks, such as connecting to the system iex shell, establishing a remote shell session, or executing some code inside a system from an external bash script.
  • OTP release forces you to properly set your run-time dependencies (:applications property in mix.exs).
  • As others have said, you need releases if you want to do proper hot-code upgrades/downgrades.

Basically, there’s no doubt in my mind that OTP releases are the proper way to run a production system. Admittedly, you can also do it with plain mix, but for all the reasons above, it’s clumsier, so I wouldn’t recommend it.


I use exrm myself, but I do have to say that we need some good way to on-prod have a well documented and first-class way to run migrations and/or seeds, this is the big thing that releases lose out on without basically rebuilding the migration structure that mix usually does for you yourself…


Ecto includes most of what is needed to run migrations as a public API in the Ecto.Migrator module, the main issue being starting every necessary application in the right order without starting the main supervisor of our application (that would start cowboy and possibly other things). Right now I’m using:

defmodule MyApp.Release.Task do
  alias MyApp.Repo

  def migrate do
    {:ok, _} = Application.ensure_all_started(:ecto)
    {:ok, _} = Repo.__adapter__.ensure_all_started(Repo, :temporary)
    {:ok, _} = Repo.start_link(pool_size: 1)

    path = Application.app_dir(:my_app, "priv/repo/migrations"), path, :up, all: true)


This can be run in distillery (I believe exrm as well) with:

$ bin/my_app command 'Elixir.MyApp.Release.Task' migrate

Whenever the migration is required (the app does not need to be started before - the command release task manages this separately).
Currently I’m running this as part of the .deb's postinst script that ensures the migrations are up to date when release is installed as a deb package. But it could be launched just after untaring the release in a regular setup. I’m planning to open source the deb repackaging tool in the near future.


Sounds like a good candidate for a new page in the Ecto docs? ^.^

And I do not recall such command invocation in exrm (and distillary does not support Windows yet, last I saw).

1 Like

Exrm had commands since 1.0, I believe. It’s similar to rpc, but rpc needs a running node it connects to, while command starts a new VM to run the function, in some ways very similar to how mix tasks work.

It might be indeed good idea to document in ecto, how to run migrations without mix.


So I guess running as a release has no difference on performance than running with mix?


Technically it probably runs faster in release due to less code being loaded in, but it would likely be so minute as to not matter anyway. :slight_smile:

1 Like

Previously, when I played with benching of Phoenix, I’ve noticed some observable differences, with OTP releases having better throughput (if my memory serves me right about 10%). I didn’t investigate this deeper, and perhaps it was some basic mistake on my behalf, but I remember seeing such differences on a few occasions in the past.