Deploying Phoenix Application to Ubuntu


Phoenix is cool, right? You’ve just created your brilliant web application and it works fine on your local machine. It’s awesome, but nobody knows about it. Stop, your friend knows, because you’ve told him in the bar a week ago and he is tied waiting to try it! Hey, it’s time to deploy the app and show it to the entire world…


For the purpose of this article, we assume that you have the following things ready:

For the purpose of this article I use several environmental variables:

Let’s prepare the ground

In order to work properly Phoenix application needs 3 things:

Let’s discuss each point in more details.

Installing and Configuring PostgreSQL

First of all, let’s install PostgreSQL from the Ubuntu software repository.

$ sudo apt-get update
$ sudo apt-get install -y postgresql postgresql-contrib

After that, we create a role for our app. Don’t use “postgres” role with your app as it gives too much privileges to our app.

$ sudo -u postgres createuser -dP ${PROJECT}_app

Then we need to change the authentication method that is used to connect to Postgres. In either case, we’ll get connection errors from postgrex. You can edit this config file with your favourite editor if you like, but this way is quicker if you have default config from repositories.

$ sudo sed -i "s/peer$/md5/" /etc/postgresql/*/main/pg_hba.conf

Optionally, you can tune other Postgres options here. But it’s out of spec for this article.
After all, restart Postgres to apply changes in configs.

$ sudo systemctl restart postgresql

Installing Erlang and Elixir

If you just try to install Erlang and Elixir from system repositories you get it a bit outdated, so I suggest to use ones from Erlang Solutions. Firstly, we need to add them to apt, then update the repository and install.

$ wget
$ sudo dpkg -i erlang-solutions_1.0_all.deb
$ sudo apt-get update
$ sudo apt-get install -y esl-erlang elixir

In order to successfully build and manage the project, we need a bit more packages.

$ sudo apt-get install -y tmux git make gcc npm webpack

Bringing up the Project

It’s time to get your project from git, build it and make running, but, firstly, I want to mention that it’s a very bad habit to run web projects from “root” user. So let’s create a user for our app and switch to.

$ sudo useradd -m -s /bin/bash $PROJECT

Next step is to create a folder for our project and give our user rights to utilise it. It’s up to you where to create it, but I suggest /opt folder as it looks suitable from the point of the convention.
We need 2 folders: one for code from git and one for server specific configuration.

$ sudo mkdir -p /opt/$PROJECT/config
$ sudo chown -R $PROJECT:$PROJECT /opt/$PROJECT

Now we switch to the user we created for our project and complete most of the project bootstrapping work from it.

$ sudo su $PROJECT
(PROJECT) $ ssh-keygen -t rsa -b 4096 -C “$EMAIL”

Here you need to add the public key from /home/$PROJECT/.ssh/ to authorised keys that can have access to your git repo through ssh. For GitHub, it is called “Deploy keys” and can be added from the repo settings page. After that, you can clone, tune and build your project.

(PROJECT) $ cd /opt/$PROJECT
(PROJECT) $ git clone ssh://$REPO repo
(PROJECT) $ cp repo/config/prod.secrets.exs.sample config/prod.secrets.exs

Now we need to create /opt/$PROJECT/config/prod.secrets.exs and add our Postgres connection settings there and maybe other stuff.

Replace all variables here with actual values. And go on with compilation and bringing it up.

(PROJECT) $ ln -s /opt/$PROJECT/config/prod.secret.exs repo/config/prod.secret.exs
(PROJECT) $ cd repo
(PROJECT) $ export MIX_ENV=prod
(PROJECT) $ mix deps.get
(PROJECT) $ mix compile
(PROJECT) $ mix do ecto.create, ecto.migrate
(PROJECT) $ cd assets && npm install && webpack --mode production && cd ..
(PROJECT) $ mix phx.digest
(PROJECT) $ iex -S mix phx.server

If we don’t get an error here, we can press Ctrl-C twice, return to our default user (press Ctrl-D or type exit ) and go on.

Configuring system.d to Start the App

System.d is the core Ubuntu initialising and service monitoring system. To make our app start automatically on boot we need to configure it. Here is a config for our app.

You need to change variables here to actual values to make it work.
After you put this file to /etc/systemd/system/$PROJECT.service, run the following commands to initialise and start it.

$ sudo systemctl enable $PROJECT
$ sudo systemctl start $PROJECT

Setting up Nginx and certbot for HTTPS

To finish our journey and make our application look professional we need to make it work through secure HTTPS protocol and listen on standard 443 port. To achieve it we need to install and configure Nginx proving our app through SSL and caching static content to enhance load speed and certbot to get free SSL certificated for us.

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository universe
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install -y certbot python-certbot-nginx nginx

Let’s configure Nginx to serve our app. Here I assume that Phoenix app is configured to listen on 4000 port (as default configuration). Add this config to /etc/nginx/sites-available/$PROJECT.

As in all previous config files you need to replace variables with their values.

$ sudo ln -s /etc/nginx/sites-available/$PROJECT /etc/nginx/sites-enabled/$PROJECT
$ sudo systemctl restart nginx

After that, Nginx is ready to work for us trough insecure HTTP protocol on 80 port. Let’s start it and check if it’s working.
Now point your browser to the $DOMAIN and check if you see the pages of your app. If everything is ok, we are moving to the last stage: adding SSL.
This command should do the job.

$ sudo certbot --nginx

Now we have our application deployed and working the correct way. To be even more secure it’s recommended to configure the firewall to allow only SSH and HTTP(S) ports, but it’s a topic of the other article.

Originally published on 3∑ blog on Medium.