Nithin Bekal About

Deploying a Phoenix app with Dokku

23 Jul 2020

Dokku is my preferred approach to deploy a web app to a VPS. It lets you set up your own Heroku style platform-as-a-service to easily deploy an app. Once set up, all you need to do to deploy is to git push your code to your dokku host.

This tutorial will walk you through deploying an Elixir + Phoenix + Ecto app, as well as connecting it to a Postgres database. We will use a blog app as the example here.

Set up the server

For my hobby projects, I use a $5/mo Digital Ocean server, with 1GB RAM and 25GB SSD storage. I installed Ubuntu 18.04 on it. (I tried 20.04, but had trouble installing dokku on it.)

Although 1GB of RAM is enough to run an Elixir app, I had trouble getting node to compile front-end dependencies with that much RAM. To get around this, set up a 1GB swap file:

cd /var
touch swap.img
chmod 600 swap.img
dd if=/dev/zero of=/var/swap.img bs=1024k count=1000
Install dokku

Now it’s time to install dokku. SSH into the server by using ssh root@your.ip.address and run the following commands. (You’ll find the command for the latest version on the dokku website.)

wget https://raw.githubusercontent.com/dokku/dokku/v0.21.3/bootstrap.sh
sudo DOKKU_TAG=v0.21.3 bash bootstrap.sh
Web installer

Once dokku is installed, you can find the web installer by going to your server’s IP address in the browser. Here you can set the following:

  • Set your hostname
  • Enable the “user virtual naming for apps” options

This lets you access apps on subdomains of your hostname. Eg. if your host is example.com, you can create an app called blog and access it at blog.example.com. This is only for convenience - you can always set up a custom domain later on.

Now would also be a good time to point the blog.example.com domain to your dokku server’s IP address.

Create the app

In this tutorial, we’re creating a blog app, and we will rather unimaginatively call it blog. SSH into the dokku host and create the app:

dokku apps:create blog

After this we need to set up some environment variables. For the SECRET_KEY_BASE variable, you can generate a value locally from your phoenix app by running mix phx.gen.secret.

dokku config:set blog LC_ALL=en_US.utf8
dokku config:set blog MIX_ENV=prod
dokku config:set blog SECRET_KEY_BASE=some_secret_key
Postgres setup

We will also use postgres as the database, so we’ll need to install the postgres plugin.

sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres

Now we will create a database called blog, and link it to the blog app.

dokku postgres:create blog
dokku postgres:link blog blog

This will setup the database and export a DATABASE_URL environment variable in our app’s docker container. Phoenix is by default configured to look for this env variable, so there’s nothing to change in our app.

Setup git remote

Now that the dokku host is ready, let’s set things up locally. We’ll first create a git remote called dokku, which will point to the dokku server.

git remote add dokku dokku@blog.example.com:blog

Once this is set up, you can run git push dokku master to deploy. However, the deploys will fail right now because we haven’t configured our repo to set up Elixir on deploy.

Buildpacks

We need to create a .buildpacks file with the following content and commit it to the repo.

https://github.com/HashNuke/heroku-buildpack-elixir.git
https://github.com/gjaldon/heroku-buildpack-phoenix-static.git

Set the Elixir and Erlang versions in elixir_buildpack.config:

erlang_version=23.0.2
elixir_version=1.10.0

Set the node version used for compiling front end assets in phoenix_static_buildpack.config:

node_version=14.4.0
Putting everything together

Now that we have everything set up, let’s deploy our app. Push the local repo to the dokku remote with git push dokku master. You should see the deploy logs as part of the output.

Now go to the IP address of the server, or the appropriate subdomain (blog.example.com in this example). The app should be running on this URL.

Running migrations

Although we have deployed the app, we still have one problem. As soon as you hit a URL that requires database access, you will get a 500 error. This is because we haven’t run Ecto migrations yet.

Before we configure dokku to run migrations as part of the deploy process, we also need to create the database. To do this, run this on the dokku server.

dokku run mix ecto.create

Next, we need to configure dokku to run the pending ecto migrations whenever we deploy. Create an app.json at the root of the repo and commit it. This will define the post-deploy command.

{
  "scripts": {
    "dokku": {
      "postdeploy": "mix ecto.migrate"
    }
  }
}

Push the code again to the dokku remote and the migrations will run at the end of the deploy.

Setting up domains

Because of the way we set up the host name through the web installer, you will see the app running on the blogsubdomain on your host. But you might want to host your app on some other domain. Run the following command to configure a custom domain for your app:

dokku domains:add blog <your-fancy-domain>
SSL

Now that we have our app up and running, let’s finish up by setting up SSL for our domain. Letsencrypt makes it incredibly easy to enable SSL for your site. Dokku has a letsencrypt plugin that enables SSL for your domain and auto-renews it when it expires. Run the following commands on the dokku server.

dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
dokku config:set --no-restart blog DOKKU_LETSENCRYPT_EMAIL=you@example.com
dokku letsencrypt blog
dokku letsencrypt:auto-renew blog
Hi, I’m Nithin! This is my blog about programming. Ruby is my programming language of choice and the topic of most of my articles here, but I occasionally also write about Elixir, and sometimes about the books I read. You can use the atom feed if you wish to subscribe to this blog or follow me on Mastodon.