lobo_tuerto's notes
for greatly improving DX
Blog
Notes
About

Deploy a Phoenix application to a VPS

Short guide on how to deploy a Phoenix app to Linode/DigitalOcean by hand.

📅Date27 July 2020
🏷️
devopselixirphoenix

Prerequisites

First, we need a Phoenix app.

The application we’ll be deploying is the one from:
Building a JSON API in Elixir with Phoenix.

I’ll show the steps needed to have this app available at: http://lobotuerto.com:4000

We’ll go with Ubuntu for the OS.
Also, we should have a domain already configured on that machine.

So, when trying something like this, it should respond as expected:

curl -H "Content-Type: application/json" -X POST \
-d '{"email":"asd@asd.com","password":"qwerty"}' \
http://lobotuerto.com:4000/api/users/sign_in \
-c cookies.txt -b cookies.txt -i

OK, SSH into your VPS then follow the steps below.

Install PostgreSQL on the VPS

Follow this guide: How to install PostgreSQL in Linux.

Make sure to create a DB user for your app —it’s on the guide.
In this example the DB user will be called deployer and will have a password deployer123.

Install dependencies for Erlang and Elixir

sudo apt install build-essential autoconf m4 libncurses5-dev libwxgtk3.0-dev \
libgl1-mesa-dev libglu1-mesa-dev libpng-dev libssh-dev unixodbc-dev

Create a user for deployment

sudo adduser deployer

Install Erlang and Elixir

Impersonate the user with:

sudo su deployer -l

This is done so the deployer user have access to the Elixir executable.

Follow this guide: How to install Elixir in Linux.

Clone your app repo

Or transfer the code using scp.

git clone https://github.com/lobo-tuerto/my-phoenix-json-api

Install the app dependencies

cd my-phoenix-json-api
mix deps.get

Adjust the config files

Comment out this code in config/prod.exs:

-config :my_app, MyAppWeb.Endpoint,
-  url: [host: "example.com", port: 80],
-  cache_static_manifest: "priv/static/cache_manifest.json"
+
+# config :my_app, MyAppWeb.Endpoint,
+#   url: [host: "example.com", port: 80],
+#   cache_static_manifest: "priv/static/cache_manifest.json"

Uncomment this code from config/prod.secret.exs:

-#     config :my_app, MyAppWeb.Endpoint, server: true
+config :my_app, MyAppWeb.Endpoint, server: true

Setup the DB

Execute the following three commands:

SECRET_KEY_BASE="`mix phx.gen.secret`" \
DATABASE_URL="ecto://deployer:deployer123@localhost/myapp_prod" \
MIX_ENV=prod mix ecto.create

SECRET_KEY_BASE="`mix phx.gen.secret`" \
DATABASE_URL="ecto://deployer:deployer123@localhost/myapp_prod" \
MIX_ENV=prod mix ecto.migrate

SECRET_KEY_BASE="`mix phx.gen.secret`" \
DATABASE_URL="ecto://deployer:deployer123@localhost/myapp_prod" \
MIX_ENV=prod mix release

Let’s create a couple of users to try it out.

_build/prod/rel/my_app/bin/my_app start_iex

iex> MyApp.Account.create_user(%{email: "asd@asd.com", password: "qwerty"})
iex> MyApp.Account.create_user(%{email: "some@email.com", password: "some password"})

This could have been done using the priv/repo/seeds.exs file too, but I wanted to showcase how to open an IEx session on the production app.

Now, without exiting that IEx session, go ahead and try this:

curl -H "Content-Type: application/json" -X POST \
-d '{"email":"asd@asd.com","password":"qwerty"}' \
http://lobotuerto.com:4000/api/users/sign_in \
-c cookies.txt -b cookies.txt -i

You should see:

HTTP/1.1 200 OK
cache-control: max-age=0, private, must-revalidate
content-length: 48
content-type: application/json; charset=utf-8
date: Mon, 29 Jul 2019 22:15:13 GMT
server: Cowboy
x-request-id: FbYAie3B0hrQ5-gAAAPF
set-cookie: _my_app_key=SFMyNTY.RgcVe-7fJ-NqSO5DQL; path=/; HttpOnly

{"data":{"user":{"email":"asd@asd.com","id":1}}}

Awesome! 🎉

Further reading on releases and deployments


Got comments or feedback?
Follow me on
rev-3b8671f