I recently stood up a side project written with Phoenix and Elixir on a Digital Ocean Droplet. Here’s the process I used, with code snippets and links to tutorials.

  1. Create a Digital Ocean Droplet
  2. Create a New User and Disable Root
  3. Install Erlang, Elixir, and NodeJS
  4. Install PostgreSQL
  5. Install nginx and Certbot
  6. Connect the Droplet to the GitHub Repository
  7. Compile the Phoenix Application
  8. Stand-up and Seed the Database
  9. Run the Phoenix Application
  10. Set Up Port Forwarding
  11. Point a Domain to Digital Ocean

For the most part, I followed along with the Up and Running - Elixir Phoenix on Digital Ocean by omgneering video tutorial on YouTube. In all of the commands below, I swapped out my actual domain and username and IP address for nonsense like “johnnyrocket.space.” I don’t own that domain.

Create a Digital Ocean Droplet

References:

I signed up for Digital Ocean and ran through the interface to create a new Droplet.

I used the Ubuntu 20.04 (LTS) x64 operating system.

The cheapest plan is going to be the $5 per month shared CPU Basic Plan, which is the left-most option.

I chose the datacenter region closest to me.

For authentication, I added a new SSH key. I think this is materially more secure than using a password. When adding a new SSH key in Digital Ocean, remember that it’s the public key (the .pub file) that you’ll copy and paste into Digital Ocean. It’s always the public key that gets shared externally.

The commands below are what I ran to set up an SSH key.

# Generate a new SHH key
ssh-keygen

# Run through entering a name ("Enter file in which to save the key"),
# providing a password, etc.

# Print the contents of the public key to your terminal
cat ~/.ssh/your-new-key.pub

# Copy the public key to use in the Digital Ocean interface

Create a New User and Disable Root

References:

I connected to the Droplet via SSH, created a new user for myself, and disabled the default root user as a security measure.

The commands below are what I ran.

# SSH into your droplet as root with its IP address
# The IP address of your server should be next to the name
# of the droplet when you log-in
ssh root@12.123.12.123

# Create a new user (provide password, other information as prompted/desired)
adduser johnny

# Add user to "sudo" (superuser do) group
usermod -aG sudo johnny

# Change to home directory
cd ~

# Create a new directory for SSH keys if it doesn't exist
mkdir -p ~/.ssh

# Create authorized_keys file and paste public key
# `cat your-new-key.pub` the public key you made on your local machine while
# setting up your Digital Ocean droplet
# Paste into authorized_keys file and save
vim ~/.ssh/authorized_keys

# Exit and log in with new username
exit
exit
ssh johnny@12.123.12.123

# Change file permissions
chmod 644 ~/.ssh/authorized_keys

# Disable root login
sudo vim /etc/ssh/sshd_config
# ... then find and change the line:
# PermitRootLogin no

# Restart ssh service
sudo service sshd restart

Install Erlang, Elixir, and NodeJS

References:

The commands below are what I used to download and install Elixir, Erlang, and Node on my Droplet. I recommend getting up-to-date commands from the Erlang Solutions and Node repository sites.

# Get the latest registered versions of your packages
sudo apt-get update

# Install various compilers and libraries you'll likely need
sudo apt-get install -y build-essential

# Install erlang
wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
sudo dpkg -i erlang-solutions_2.0_all.deb

# Register the packages you just downloaded
sudo apt-get update

# Install erlang and elixir
sudo apt-get install erlang
sudo apt-get install elixir

# Install node
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs

# Register the packages
sudo apt-get update

Install PostgreSQL

References:

The app in the YouTube series I was watching used MySQL, but my project used PostgreSQL. To set up PostgreSQL, I followed a tutorial from Digital Ocean’s blog called How To Install PostgreSQL on Ubuntu 20.04 [Quickstart]. The blog post Getting Started with PostgreSQL on Mac OSX also has some useful commands for setting up PostgreSQL once it’s installed on your server.

I installed PostegreSQL, logged into psql, created the user that my Phoenix app will use, and gave the uesr all permissions that the default postgres superuser has.

# Install postgresql
sudo apt install postgresql postgresql-contrib

# Log into postgres as the postgres user
sudo -u postgres psql

# Create a user 'johnny' with a password 'quoted password'
CREATE ROLE johnny WITH LOGIN PASSWORD 'quoted password'

# List all users and their roles for reference
\du

# Start giving 'johnny' every permission that 'postgres' currently has
ALTER ROLE johnny SUPERUSER;
ALTER ROLE johnny CREATEROLE;
ALTER ROLE johnny CREATEDB;
ALTER ROLE johnny REPLICATION;
ALTER ROLE johnny BYPASSRLS;

# Exit
\q

Install nginx and Certbot

References:

I installed nginx on my server, configured my domain as a separate server block, and used CertBot to get a certificate for https.

The commands below cover the process of installing nginx, accessing configuration files, and allowing the necessary ports per Uncomplicated Firewall (ufw). I recommend following along with the omgneering YouTube tutorial for the exact changes to be made to an nginx configuration file.

To get a certificate for my domain for https, I followed the CertBot instructions on the EFF website. I did, however, swap out the command sudo certbot --nginx in the official instructions with sudo certbot --nginx -d johnnyrocket.space -d www.johnnyrocket.space, which I’ve seen used in the Full Stack for Front-End Engineers course and in the omgneering YouTube tutorial.

# Install nginx
sudo apt install nginx

# Allow ports and traffic
sudo ufw enable
sudo ufw allow OpenSSH
sudo ufw allow http
sudo ufw allow https
sudo ufw allow 4000
sudo ufw enable

# Make an nginx config for your project as a separate server block
sudo mkdir -p /var/www/johnnyrocket.space/html
sudo chown -R $USER:$USER /var/www/johnnyrocket.space/html

# Copy the settings from the default file to your project
sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/johnnyrocket.space

# Edit the file with vim
# watch video from here: https://youtu.be/HMhLn-qdQNE?t=201
# remove "default_server" from config
# change root/var/www/html; to root /var/www/johnnyrocket.space/html
# add domains to server_name, e.g. server_name johnnyrocket.space www.johnnyrocket.space
sudo vim /etc/nginx/sites-available/johnnyrocket.space

# Create a symbolic link
sudo ln -s /etc/nginx/sites-available/johnnyrocket.space /etc/nginx/sites-enabled

# Enable server blocks
# uncomment "server_names_hash_bucket_size 64;"
suod vim /etc/nginx/nginx.conf

# Check for syntax errors in your nginx configuration
sudo nginx -t

# Restart
sudo systemctl restart nginx

Connect the Droplet to the GitHub Repository

References:

First, I generated a new SSH key on my server.

# Try to install git (it's probably already installed)
sudo apt-get install git

# Generate a new ssh key
ssh-keygen

# Display the contents of your new public key to terminal
# By default, it would have been id_rsa.pub, but you may have named it something else
cat ~/.ssh/id_rsa.pub

I navigated to my project repository on GitHub, hit “Settings” in the top-middle of the page, then “Deploy keys” on the left sidebar, then “Add deploy key” on the top right of the page. I gave the key a title, and pasted the contents of the newly generated public key on my server into the “Key” text box in GitHub.

I navigated back to the main page of my project repository, clicked the green “Clone or download” button in the top right, and copied the SSH link (the little “Clone or download” box said “Clone with SSH”).

Back in my terminal, I ran git clone with the link I just copied.

git clone git@github.com:johnny/johnnyrocket.git

# Navigate into your cloned project
cd johnnyrocket

Compile the Phoenix Application

References:

I installed my Elixir dependencies, client-side dependencies, built my client-side assets, made my configuration secrets detectable, and built a Phoenix release.

I followed along with the omgneering YouTube tutorial to install my project dependencies, but I did not set up my configuration in the same way. He hard coded his configuration secrets. I followed something closer to what’s in the Phoenix guide, where I stored secrets in my terminal session. You may have more secrets than just a secret key and database URL. For instance, if you’re using Ueberauth, you should have secrets from the login provider (e.g. Google).

Mashing the YouTube and Phoenix guides together, I came to:

# Install Elixir/Phoenix project dependencies
mix deps.get
# hit Y to get hex manager

# Install Node/client-side dependencies
cd assets
npm install
sudo npm install -g webpack
sudo npm install -g webpack-cli
cd ..

# Use mix to generate a REALLY_LONG_SECRET
mix phx.gen.secret

# Add secrets to terminal session
export SECRET_KEY_BASE=REALLY_LONG_SECRET
export DATABASE_URL=ecto://USER:PASS@HOST/database
export GOOGLE_CLIENT_ID=asdfasdfasdfasdf
export GOOGLE_CLIENT_SECRET=asdfasdfasdfasdfasdfasdfasdfasd

# Compile assets
npm run deploy --prefix ./assets
mix phx.digest

# Initial setup
MIX_ENV=prod mix compile

# Compile release
MIX_ENV=prod mix release

Stand-up and Seed the Database

The video tutorial did not cover seeding a database. I thought I was going to need to create some kind of script to be included in my application build. Ultimately, it was simpler than I thought. I just ran the same command with MIX_ENV=prod, and it worked. In my application, I have some data that’s needed for some calculations no matter what environment my application is running in (e.g. production or development)

# Create database
MIX_ENV=prod mix ecto.create

# Run migrations
MIX_ENV=prod mix ecto.migrate

# Run the seed script in production mode
MIX_ENV=prod mix run priv/repo/seeds.exs

Run the Phoenix Application

References:

In the Node ecosystem, you might have to install a process manager like PM2 to be able to run your application on its own and walk away. With Phoenix, you can just invoke the release build followed by daemon to run it as a background process. I ran:

_build/pod/rel/johnnyrocket/bin/johnnyrocket daemon

Set Up Port Forwarding

References:

To set up port forwarding, I followed along with the video tutorial at approximately 7:25. The commands in the terminal are replicated below, but the video will show the changes needed to the nginx configuration file.

sudo vim /etc/nginx/sites-available/johnnyrocket.space

# add upstream phoenx { server 127.0.0.1:4000; }
# delete everything in the "location" selection, add allow all;, add proxy headers

# check your changes for syntax errors
sudo nginx -t

# Restart nginx when the changes have been made
sudo systemctl restart nginx

Point a Domain to Digital Ocean

References:

I visited my domain registrar’s website to create 3 nameserver entries pointing to Digital Ocean. I then visited Digital Ocean to add my domain and set up 2 A Records, one for @ and www. When I added a domain, Digital Ocean autopopulated an @ A record with my domain and the server’s IP address. I just added a second, identical record that has www before the domain (e.g. www.johnnyrocket.space).