Deploy Phoenix to Ubuntu VPS - Server Preparation

In this series, we're going to cover deployments using Distillery to build releases and Edeliver for automating deployment tasks.

As with any technology, when you're ready to deploy your application to production there is normally a learning-curve involved. Elixir and Phoenix apps are no different.

In this series, we're going to cover deployments using Distillery to build releases and Edeliver for automating deployment tasks.

We'll be deploying our Phoenix application to a VPS on Digital Ocean.  If you don't have an account, create one before you begin.

There's a lot to cover so let's dive right in.

Somethings to consider:

  • It's possible to fully automate these steps but we're going to get our hands dirty and do it manually.
  • We're assuming you have some experience managing Ubuntu servers and are comfortable running commands in the terminal.

Series

Overall Deployment Goals

It's always good to have a plan with clearly defined goals so here are our high level objectives for this series:

  • Build and Deploy on a Single Server
  • Local Postgres Database
  • Nginx as a reverse proxy
  • SSL Certificate with live domain
  • Systemd for auto-restarts
  • Managing production deployment:
    - Deploy / Rollback / Restart application releases
    - Run production database migrations
    - Check production application logs

Ubuntu Server Preparation

Elixir is a compiled language and therefore needs to be "built" into releases before being started.  It also needs to be built on the same operating system it is intended to run on, Ubuntu 18.04 in our case.

Edeliver & Distillery are our weapons of choice and just so we understand the process and how these tools work together here's an overview:

mix edeliver build release

> Edeliver bashes into remote server
> pulls code from repo
> uses Distillery to build the release on remote server
> downloads gzipped release to local store (.deliver/releases)

mix edeliver deploy release

> Edeliver uploads release to remote server
> unpacks the files to the target directory
> starts the release on remote server

I wanted to mention these details to outline the differences between build host vs deploy host and this should lay a good foundation for when more complex deployments are needed.

Create a Server

We use Digital Ocean to host most of our servers so create an account if haven't already.  

Create a new droplet, choose Ubuntu 18.04, your region and fire it up.

NOTE: Keep in mind that while running a Phoenix application takes very little memory,  we'll need at least 2GB of memory to build the application.  With any less the build process will run out of memory and fail.

Create deploy User

First we'll create a non-root user for deploying, aptly named deploy.

1. Log in to your new server as root and run:

$ sudo adduser deploy

Enter a password, and skip filling out details if you like.

2. With the deploy user created we need to add it to sudoers and to allow them to run sudo commands.

$ usermod -aG sudo deploy

3. Next, let's allow the user to use sudo without entering a password.

$ echo "deploy  ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/deploy

4. Run this series of commands to configure the user’s .ssh directory:

sudo mkdir -p /home/deploy/.ssh
sudo touch /home/deploy/.ssh/authorized_keys
sudo chmod 700 /home/deploy/.ssh
sudo chmod 644 /home/deploy/.ssh/authorized_keys
sudo chown -R deploy:deploy /home/deploy

5. Add your local ssh public key to authorized_keys

In another terminal window, on your local machine:

$ cat ~/.ssh/id_rsa.pub

(copy key output)

On the target server, paste the key into authorized_keys:

$ sudo vim /home/deploy/.ssh/authorized_keys

Now you should be able to login to the server as the deploy user:

$ ssh deploy@<server-ip >

deploy@deploy-example:~$

SSH Config

To simplify server login and deployment let's add an entry for our server to ~/.ssh/config .

This entry will also be used to configure edeliver for deployment.

Host deploy.do
 User deploy
 HostName <do-ip-address>

We're using .do for Digital Ocean, but this can be anything you like.

Now you can safely login to the server with:

$ ssh deploy.do

We will also use this host entry to configure edeliver.

Server Hardening

There are many ways to "harden" your server which is a vital step toward keeping your data and application secure, but for the purpose of this tutorial we'll skip this and keep on trucking.

Please do some reading on how you can harden your server when you get a chance.

Install Asdf

asdf-vm is a CLI tool that can manage multiple language runtime versions on a per-project basis.

Ensure you're logged in as the deploy user and following the instructions from the docs:

$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.7.6

Then add these lines to .profile so edeliver can evaluate them on deploy:

echo -e '\n. $HOME/.asdf/asdf.sh' >> ~/.profile
echo -e '\n. $HOME/.asdf/completions/asdf.bash' >> ~/.profile
source ~/.profile

And a quick sanity check:

$ asdf --version
v0.7.6-6207e42

Install Erlang

Erlang is required because this is our build and runtime server.  There are some dependencies required to install Erlang:

$ sudo apt update
$ sudo apt -y install build-essential autoconf m4 libncurses5-dev libwxgtk3.0-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libssh-dev unixodbc-dev xsltproc fop

Then we can use asdf to install Erlang:

$ asdf plugin-add erlang https://github.com/asdf-vm/asdf-erlang.git
$ export KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac"
$ asdf install erlang 22.2.1

...

$ asdf global erlang 22.2.1

Check installation:

$  erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().'  -noshell
"22"

Install Elixir

Logged in as deploy: (change version if necessary)

$ asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
$ asdf install elixir 1.10.0
$ asdf global elixir 1.10.0

Install NodeJS

Logged in as deploy: (change version if necessary)

$ sudo apt -y install dirmngr gpg
$ asdf plugin-add nodejs https://github.com/asdf-vm/asdf-nodejs.git
$ bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring
$ asdf install nodejs 12.14.1
$ asdf global nodejs 12.14.1

Install PostgreSQL

We're going to use a local DB instance so let's install PostgreSQL.  

Aside: There are some important steps to take to secure your DB, but again I'll leave those decisions up to you.

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

Ubuntu comes with version 10, and this will work for us.  If you need to install a later version, you can find details here.

Verify that you can access psql:

$ sudo -u postgres psql

postgres=# \q

\q to exit.

Create DB And User

Create a new user and database for our production app.

First set some variables as deploy: (be sure to change the variables to suit your app)

DB_USER="deploy"
DB_PASS="supersecret"
DB_NAME="myapp_prod"

Then we can add run the queries to create the user and DB:

sudo -u postgres psql -c "create user $DB_USER with password '$DB_PASS'";

And grant all privileges to the user:

sudo -u postgres psql -d "$DB_NAME" -c  \
 "grant all privileges on database $DB_NAME to $DB_USER;"

Record the details somewhere secure as we'll need them later when we deploy the application.

Install Nginx

Pretty straightforward here:

$ sudo apt update
$ sudo apt -y install nginx

Verify it's installed:

$ sudo nginx -v
nginx version: nginx/1.14.0 (Ubuntu)

Configuration for Nginx will be handled in a separate post.  We're just focused on preparing the production environment for now.

Create an A Record for Domain

Let's create an A record pointing to the server IP address so it'll propagate by the time we have our app in production.

We use cloudflare for DNS but log in to your domain registrar and create the A Record either on the root or sub-domain.

Nginx should be running at this point so you should see the default page if you load the your domain in the browser:


Systemd

Comes with Ubuntu so no action to take here.

Wrapping Up.

So far we have laid the groundwork and focused on preparing the production server for building and running a Phoenix Application.

It can be tedious to manage your own server so this series has intentionally tried to keep things as simple as possible to reduce the surface area for issues to crop up while deploying our Phoenix App.

In the next part of this series, we'll prepare our phoenix application for deployment using Distillery to build releases and Edeliver.

Series

Troy Martin
Senior Engineer working with Elixir and JS.
Keep in the Loop

Subscribe for Updates

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.