When we start to materialize our app from an idea into code, in the first days, weeks, and months, we create, develop and test our app locally, on our machine.

Table of contents

    Inevitably, our development will result in a semi-ready or fully developed application, and regardless of whether we build our business application, for a public or private project we will have to package, and deploy Phoenix app somewhere, either we want to release it locally or on some host.

    Roadmap:

    Today we will answer how to neatly and easily package our Phoenix app into a self-contained release.

    We will set up mix release config and then go through other steps needed for the deployment process to succeed.

    Resulting in ready-to-be-dropped into production machine package.

    Let's start with a little introduction.

    How we used to generate apps (most of the time)

    Let's start with a little introduction. Before Elixir version 1.9+, the most reliable way to make a standalone executable from the codebase, dependencies, Erlang VM, was a library called Distillery.

    Distillery your mix project and produced Erlang/OTP release.

    Goodbye Distillery

    With Elixir 1.9 release, most functionality of Distillery was merged into Mix itself, and the library was deprecated. If you used Distillery for your releases before, the transition to mix releases shouldn't be dramatic, although, there are some minor differences, be aware of that.

    Welcome mix release

    Now, we will explore how to set up and use mix release on the dummy phoenix app, be more than welcome to tag along.

    The first step is to use mix command to create a basic project for us:

    mix phx.new app

    With our app created, you could right now build your release running mix release, output being:

    * assembling app-0.1.0 on MIX_ENV=dev
    
    * using config/runtime.exs to configure the release at runtime
    
    Release created at _build/dev/rel/app
    
        # To start your system
    
        _build/dev/rel/app/bin/app start
    
    Once the release is running:
    
        # To connect to it remotely
    
        _build/dev/rel/app/bin/app remote
    
        # To stop it gracefully (you may also send SIGINT/SIGTERM)
    
        _build/dev/rel/app/bin/app stop
    
    To list all commands:
    
        _build/dev/rel/app/bin/app

    This command compiled release and saved it under the path: _build/[ENV]/rel/[app name] (ENV depends on which environment you invoked mix release, and app name is just the name of your app, so in our example dev and app)

    There reside all files needed to start our app, often we are only interested in _build/dev/rel/app/bin/app, because it holds all commands needed to start, connect to, stop our application, and even invoke functions using eval (for list of all commands refer to output above)

    If you open _build/dev/rel/app/bin/ folder you can see that by default there is also a .bat file to control our app in windows environment. Of course, you could change that behavior, but how?

    Configuring mix releases

    To configure how mix release behaves we must go into mix.exs file and then we will focus on the project section.

    def project do
    [
        app: :app,
        version: "0.1.0",
        elixir: "~> 1.14",
        elixirc_paths: elixirc_paths(Mix.env()),
        start_permanent: Mix.env() == :prod,
        aliases: aliases(),
        deps: deps()
    ]
    end

    Here we can add a releases key and in this key will reside all of the configuration for mix release. To be more specific, under your release name, your config will be staying.

    You can have many different releases if you want to.

    For example, let's add a basic config for production that removes executables for windows and keeps some information about BEAM files, we will call this release prod.

    releases: [
        prod: [
            include_executables_for: [:unix],
            strip_beams: [keep: ["Docs", "Dbgi"]]
        ]
    ]

    Cool, now to use this config for our release we simply call mix release with the name of desired configuration: prod.

    So:

    mix release prod

    There are of course many other options, if you are interested, please refer to:

    https://hexdocs.pm/mix/main/Mix.Tasks.Release.html

    A few important notes before we can proceed with deployment

    We will release locally but the steps I will show you could be easily packed into some build script for the auto-deployment process on a remote machine.

    There is one important matter I need to mention, when you want to deploy a release, it requires the same operating system distribution and version as the one that the release was built on.

    One more thing before we start, in Elixir 1.11, config/runtime.exs file was introduced.

    This configs file's purpose is to provide application configuration at runtime(meaning after compilation of our project), as opposed to compile time config files.

    A few other important aspects of this file is that it's running in all

    environments and that config/runtime.exs is meant to run in an environment without Mix, so functions you would use to set application variables(such as Mix.env() or Mix.target() )are not available. Instead, they were added to the Config module.

    The deployment

    The first step is to take care of environment variables.

    For our case, we only need to set DATABASE_URL (I believe it is self-explanatory) and SECRET_KEY_BASE (if you don't know, this secret is used by Endpoint configuration, it's a base to generate secrets for encrypting and signing data)

    You can generate a secret using:

    mix phx.gen.secret

    Now, either you can manually type:

    export SECRET_KEY_BASE=REALLY_LONG_SECRET
    export DATABASE_URL=ecto://USER:PASS@HOST/database

    into console or paste it into a .env file in the main directory of our project then type source .env

    We should not forget to prepare dependencies for our project, we need to fetch them(only those that are needed for production)

    and then compile them.

    mix deps.get --only prod
    MIX_ENV=prod mix compile

    And also compile the app's assets

    MIX_ENV=prod mix assets.deploy

    Finally, you could now run mix release and start release on the local machine or ship the package to the target machine and run it there, but a few more things could help you in your releases.

    mix phx.gen.release command generates three files,two files that helps us in running migration inside the release:

    lib/app_name/release.ex - this module contains tasks to run migrations inside a release

    rel/overlays/bin/migrate - and this one is for invoking our migrations using the Release module

    third file: rel/overlays/bin/server is for running phoenix server inside a release with an environment variable: PHX_SERVER=true.

    If you wonder what is a overlays folder, it is a folder where its content will be copied into every release, so we don't have to manually copy some files that we want in every release, for example our scripts to run the web server and migrations as we saw above or Dockerfile so we can containerize our project, speaking of docker.

    phx.gen.release has the option to make for us Dockerfile and dockerignore.

    This Dockerfile contains all setup needed to produce the release and containerize it so we can have seamless deployment in docker container.

    Closing thoughts

    No complex configuration, no hours of wondering how to set up our project, just one small config and we are good to deploy elixir app and manage our package with Mix Releases. Happy deploying.

    Curiosum Elixir Developer Konrad
    Konrad Sowik Elixir Developer

    Read more
    on #curiosum blog