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.
There’s a lot to cover so let’s dive right in.
- 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.
- Part 1: Server Preparation
- Part 2: App Configuration & Deployment
- Part 3: Finishing Deployment
- Repo: https://gitlab.com/phxroad/phoenix-deploy-example
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
$ sudo vim /home/deploy/.ssh/authorized_keys
Now you should be able to login to the server as the deploy user:
$ ssh [email protected]<server-ip > [email protected]:~$
To simplify server login and deployment let’s add an entry for our server to
This entry will also be used to configure edeliver for deployment.
Host deploy.do User deploy HostName <do-ip-address>
.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.
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.
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
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 ... (will take some time) $ asdf global erlang 22.2.1
$ erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell "22"
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
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
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.
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:
Comes with Ubuntu so no action to take here.
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.