Elixir: Up And Running With Dokku On Digital Ocean

Jon Lunsford
6 min readFeb 19, 2018

--

Deploying Elixir Apps With Dokku

With many methodologies out there for deploying elixir apps, it’s hard to choose a simple way to get your app online quickly. Enter Dokku, an open source PaaS, a Docker based mini Heroku you can host yourself. Dokku allows you to easily deploy elixir (and many other types of apps) with the ease of Heroku at a fraction of the cost.

This guide assumes you are familiar with creating droplets, and configuring DNS on Digital Ocean. A familiarity with application containerization a la Docker will also be helpful, though not required.

We will walk through getting up and running in five sections:

  • Install Dokku on a new Digital Ocean droplet
  • Install a local Dokku CLI client
  • Configure an elixir app for Dokku deployment
  • Deploy to Dokku
  • Add a backing service, PostgreSQL

Installing Dokku on a New Digital Ocean Droplet

While Digital Ocean offers a one-click installation option, we will be starting from scratch. This will ensure that we are following the current recommended installation guide from Dokku.

  1. Create a new droplet with Ubuntu 16.04 x64 and at least 1GB or RAM
  2. SSH into your new droplet and install the latest stable version of Dokku:
wget https://raw.githubusercontent.com/dokku/dokku/v0.11.3/bootstrap.sh;
sudo DOKKU_TAG=v0.11.3 bash bootstrap.sh

3. Once the installation is complete, you can open a browser to setup your SSH key and virtualhost settings. Open your browser and navigate to the droplet’s IP address — or the domain you assigned to that droplet previously — and configure Dokku via the web admin. See Digital Ocean’s guide on configuring a domain name if you need to.

That’s all for the basic installation, you can follow their advanced installation guide if you’re looking for more granular options.

Install a local Dokku CLI Client

This step isn’s absolutely required, but I highly recommend it. Installing a Dokku client on your machine will give you that familiar Heroku UX.

  1. Clone their official shell client:
git clone git@github.com:dokku/dokku.git ~/.dokku

2. Add an appropriate alias:

# zsh: add the following to either .zshenv or .zshrc
alias dokku='bash $HOME/.dokku/contrib/dokku_client.sh --rm'

# fish: add the following to ~/.config/fish/config.fish
alias dokku 'bash $HOME/.dokku/contrib/dokku_client.sh --rm'

# csh: add the following to .cshrc
alias dokku 'bash $HOME/.dokku/contrib/dokku_client.sh --rm'

Notice the --rm flag, this ensures that each time you dokku some-command the container that is spun up on the remote server is one-time and is removed after your command completes, like Heroku one-off dynos. I was bitten by this when I ended up with dozens of containers running eventually, not understanding where they were coming from.

Configure an Elixir App for Dokku Deployment

Dokku uses Heroku buildbacks to detect your type of app, build it, then provision containers. While there isn’t an official elixir build pack, HashNuke/heroku-buildpack-elixir is stable on does the job. Additionally, if you are deploying an app that has assets, a phoenix app for example, you will need another build pack as well, gjaldon/heroku-buildpack-phoenix-static. Since we will deploy a vanilla phoenix app, we will use both.

To make sure our Dokku installation is working properly, let’s create a phoenix app and push it.

  1. Create a new phoenix app (without ecto for now):
mix phx.new hello-phoenix --no-ecto
cd hello-phoenix
git init
git add .
git commit -m "Initial Commit :boom:"

2. Configure the phoenix endpoint for Dokku, config/prod.exs:

config :hello_phoenix, HelloPhoenixWeb.Endpoint,
http: [:inet6, port: System.get_env("PORT")],
url: [host: System.get_env("WEB_HOST"), port: 80],
load_from_system_env: true,
cache_static_manifest: "priv/static/cache_manifest.json"

Phoenix needs to know what port to listen to, as well as the host. Both could be hard coded, but we’ll configure these using env variables via Dokku.

3. Configure config/prod.secrets.exs to use env variables, this way we can safely check the file into source control:

config :hello_phoenix, HelloPhoenixWeb.Endpoint,
secret_key_base: System.get_env("SECRET_KEY_BASE")

Comment out the following line in .gitignore so we can check it in:

# Alternatively, you may comment the line below and commit the
# secrets files as long as you replace their contents by environment
# variables.
# /config/*.secret.exs

4. Create a Procfile in your project’s root dir, just like you would for Heroku:

web: ./.platform_tools/elixir/bin/mix phx.server

5. Create a .buildpacks file in the root dir of your project, this tells Dokku which packs to use:

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

6. Create a elixir_buildpack.config file in your app's root dir. The file's syntax is bash. Here’s the minimum config you’ll need, you can see all of the defaults here.

# Erlang version
erlang_version=20.1
# Elixir version
elixir_version=1.5.2

7. Create a phoenix_static_buildpack.config file in your app's root dir. The file’s syntax is bash. Here’s the minimum config you’ll need, you can see all of defaults here.

# Use phoenix 1.3 executable
phoenix_ex=phx

NOTE FOR UMBRELLA APPS:

If you are deploying an umbrella app that includes a phoenix app, the static build pack will not understand where the app is, add this to your phoenix_static_buildpack.config to fix that:

phoenix_relative_path=/apps/my_phoenix_under_umbrella/

Make sure all of these changes are committed.

Deploying to Dokku

Now that our app is ready to go, let’s create our Dokku app and configure the production environment. All of the following commands must be executed in your app directory, just like Heroku, /hello-phoenix in our case.

Before we can utilize our Dokku CLI client we installed, we need to set the env var DOKKU_HOST on our local machine:

export DOKKU_HOST="your-droplet-host"

Now that Dokku knows where our remote server is, lets configure our environment.

  1. Create a new app:
dokku apps:create hello-phoenix

This will add a dokku remote to our repo, confirm that it worked with git remote -v, you should see something like:

dokku   dokku@your-droplet-host:hello-phoenix (fetch)
dokku dokku@your-droplet-host:hello-phoenix (push)

2. Set required env variables:

dokku config:set --no-restart PORT=5000
dokku config:set --no-restart SECRET_KEY_BASE="my_secret_key"
dokku config:set --no-restart WEB_HOST="my-droplet-host"

Dokku forwards port 80 externally to 5000 internally by default, now phoenix will listen for connections there. mix phx.gen.secret will give you a new random key to use for SECRET_KEY_BASE.

3. Push to Dokku:

git push dokku master

4. View your deployed app at:

hello-phoenix.your-droplet-host.com

If all goes well, you should see the default welcome to phoenix page. If not, leave a comment and i’ll try to help. Now that we’ve proven our Dokku install works, let’s go ahead and walk through adding a backing service and linking it to our phoenix app.

Add a Backing Service, PostgreSQL

This is where a familiarity with containerization my help, the idea of linking containers together in Dokku, is exactly the same as Docker, it’s simply an abstraction of the concept.

  1. Add ecto and postgrex as dependencies in mix.exs:
defp deps do
[
...
{:postgrex, ">= 0.0.0"},
{:ecto, "~> 2.1"}
]
end

2. Install them:

mix deps.get

3. Create our repo module lib/hello_phoenix/repo.ex:

defmodule HelloPhoenix.Repo do
use Ecto.Repo, otp_app: :hello_phoenix
end

4. Register our new ecto repo, config/config.exs:

config :hello_phoenix, ecto_repos: [HelloPhoenix.Repo]

5. Configure our production DB in config/prod.exs:

config :hello_phoenix, HelloPhoenix.Repo,
adapter: Ecto.Adapters.Postgres,
url: System.get_env("DATABASE_URL"),
pool_size: 20

6. Install the Postgres Dokku plugin on your droplet:

# on your Dokku host
# install the postgres plugin
# plugin installation requires root, hence the user change
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git

7. Create the remote database, from your local machine, run:

dokku postgres:create hello-phoenix-database

8. Once the service creation is complete, set the DATABASE_URL environment variable by linking the service:

dokku postgres:link hello-phoenix-database hello-phoenix

If you run into issues running postgres:linkfrom your local machine, you can also SSH into the remote machine and run it there. Potentially requiring sudo if the Dokku group is not configured on the remote machine.

9. Deploy our app with the new Postgres config:

git push dokku master

These steps outlined how to add a backing service, PostgreSQL, you could follow these exact same steps to add Redis, or any other backing service Dokku supports via plugins.

In Summary

We’ve successfully built up our own Heroku and deployed an elixir app from scratch. I use this setup as a playground of sorts, while this may not be Super Huge Production Ready ™️ (SHPR), I can at least have fun writing elixir and get my ideas online ASAP.

As an added bonus, you get all of the official Heroku buildpacks for free, so if you’re building non elixir apps as well, its literally just a git push dokku master, and applying the same Adding a Backing Service principle laid out in this post for whatever you need to support your app.

You can find the repo for this post here: jonlunsford/hello_phoenix

--

--

Jon Lunsford

Engineer @convertkit, programming enthusiast, father, and musician