Phoenix app deployment with Ansible playbooks for Elixir

Elixir

This year we launched our first commercial project IEEP.eu using the Phoenix Framework. We did a lot of research about how to set up the deployment process for a production Phoenix app. We were looking for a tutorial on the full process to go from nothing to production deployment but the tutorials out there only showed bits and pieces. The configuration of all elements was time-consuming but we finally got a working solution. To save everyone else the effort, we are open sourcing our deployment process. Therefore, we released a repo with Ansible playbooks and this simple tutorial that explains how to deploy an example Phoenix 1.3 application.

Let’s start with a big picture:

  • We want to deploy the Phoenix app to staging and production from our local machine and CI server
  • We want to compile the app only once and use the same compiled version across staging/production
  • We don’t want to have credentials compiled into our app release package

To achieve those goals we will provision the Build Server with Erlang, Elixir and Node.js installed there. A second server will be used for staging/production. For the sake of this tutorial we will provision just a production server but you can create as many staging or production servers as you need.

The App Server will have only PostgreSQL, Nginx with Let’s Encrypt SSL certificate and a few other tiny tools installed. We don’t need to have Erlang/Elixir on the App Server because the release of the Phoenix application is compiled on the Build Server and it contains everything needed to start the application. Thanks to that, when we want to upgrade Erlang or Elixir we only need to do it on the Build Server. After that, we can compile our application there and just deploy it to the production server.

Here is the picture of the servers’ architecture:

--------------------                       --------------------------
|                  |                       |                        |
|                  | --- (1) build ------> |                        |
|                  |                       |      build server      |
|                  | <-- (2) ------------- |                        |
|                  | copy to release store |                        |
|  control machine |                       --------------------------
|                  |
| (localhost /     |                       --------------------------
|       Circle CI) |                       |                        |
|                  | --- (3) deploy -----> |    target machine      |
|                  |                       |                        |
|                  | --- (4) ------------> | (staging / production) |
|                  |   restart & migrate   |                        |
--------------------                       --------------------------

As you can see the deployment can be triggered from local machine or CI server. We use edeliver tool to connect with Build Server. After the Phoenix application is compiled on the Build Server then the release package of our application is downloaded to local store on the local machine or CI server. The next step is to deploy the release to production server and restart the Phoenix app there.

Provision the Build Server

Ansible

We prepared open source Ansible playbooks for Elixir that you can use to provision the Build Server for the Phoenix application.

  • First thing to do after you clone the repository is to install the dependencies:
    $ ansible-galaxy install -r requirements.yml
    
  • You will need to generate your vault_pass.txt file inside of the repository. The file will be used to encrypt credentials like DB password. Thanks to that you can commit the encrypted credentials into the repository. The vault_pass.txt is in .gitignore and you should keep it somewhere safe in order to decrypt values in the future.
    $ openssl rand -base64 256 > vault_pass.txt
    
  • You need to create a new file for the domain of your build server for instance apps/phoenix-website/host_vars/elixir-build-server.lunarlogic.io.
  • Please add your public key to the public_keys folder. You can add more public keys there if you want to give access to the Build and App Servers for other people. Don’t forget to remove our public keys unless you want to give us access to your servers. :)
  • You can also generate a public key for your CI server. We use CircleCI and detailed instructions for how to generate public key and setup CircleCI can be found here. Note: if you don’t want to use CI then remove circle_ci.pub from authorized_key_paths in playbook.

Now when Ansible playbook for Build Server is configured we can provision the server with the command:

$ ./play apps/elixir-build-server

If you want to ssh to the server then you can do:

$ ssh admin@phoenix-website.lunarlogic.io

The admin user has sudo access. The builder user is used to build the Elixir applications.

Provision the App Server

The next step is to configure the App Server where the Phoenix application will be deployed.

  • You need to create a new file for the domain of your App Server for instance apps/phoenix-website/host_vars/phoenix-website.lunarlogic.io and put the encrypted password there. Please take a look at the example file.
    $ ansible-vault encrypt_string --name db_password "YOUR_DB_PASSWORD"
    
  • The next step is to provision the App Server:
    $ ./play apps/phoenix-website
    

And that’s it! The App Server has an admin user with sudo access and the phoenix user that will run Phoenix application.

The App Server already has Nginx installed and SSL configured thanks to Let’s Encrypt.

Phoenix application deployment configuration

Phoenix Framework

In order to configure your Phoenix application for deployment you will have to add the distillery tool for release packaging. For the deployment we use edeliver. You can find the detailed steps for how to configure distillery and edeliver here.

Production credentials

You need to prepare sys.config file with the configuration and credentials for the Phoenix application. In our case, the sys.config file is named basing on application name and it is placed on production server at the path /home/phoenix/phoenix_website/phoenix_website.config. The content of this file looks like this if you have a new fresh Phoenix 1.3.0 application.

[{sasl,[{errlog_type,error}]},
 {logger,
     [{console,
          [{format,<<"$time $metadata[$level] $message\n">>},
           {metadata,[request_id]}]},
      {level,info}]},
 {phoenix_website,
     [{ecto_repos,['Elixir.PhoenixWebsite.Repo']},
      {'Elixir.PhoenixWebsiteWeb.Endpoint',
          [{render_errors,
               [{view,'Elixir.PhoenixWebsiteWeb.ErrorView'},
                {accepts,[<<"html">>,<<"json">>]}]},
           {pubsub,
               [{name,'Elixir.PhoenixWebsite.PubSub'},
                {adapter,'Elixir.Phoenix.PubSub.PG2'}]},
           {load_from_system_env,false},
           {url,[{host,<<"example.com">>},{port,80}]},
           {cache_static_manifest,<<"priv/static/cache_manifest.json">>},
           {server,true},
           {secret_key_base,<<"FILL_IN_HERE">>},
           {http,[{port,8888}]}]},
      {'Elixir.PhoenixWebsite.Repo',
          [{adapter,'Elixir.Ecto.Adapters.Postgres'},
           {username,<<"FILL_IN_HERE">>},
           {password,<<"FILL_IN_HERE">>},
           {database,<<"FILL_IN_HERE">>},
           {pool_size,15}]}]}].

You need to set your own credentials in place of FILL_IN_HERE. Please note, that if you configure deployment for your existing Phoenix project, then your configuration might be different. In that case you need to compile application to prepare example sys.config file. Learn more about edeliver sys.config here and please refer to the comment above the line LINK_SYS_CONFIG="$DELIVER_TO/$APP/$APP.config" in the file .deliver/config to see the steps to follow to generate the sys.config for your project.

Deployment of Phoenix application

You are ready to deploy the Phoenix Website application.

BRANCH=master TARGET_SERVER=production bin/deploy

You can also deploy the application from a CI server. Here is example for CircleCI 2.0 Phoenix deployment.

Summary

Here is our Phoenix Website running on production. As you can see, we covered the whole process of deployment from provisioning the servers to application configuration and finally the deploy from local machine and CI server.

We hope this tutorial will save you a lot of time. Let us know in the comments below how you deploy your Phoenix or other Elixir applications. Especially, if you have any tips or recommendations.

Share this on: