How to deploy an Elixir Plug application to Heroku

This guide will work for any Plug app including Phoenix.

In my previous post I outlined how to create a basic Plug application. We’ll use that application as an example as we walkthrough how to deploy to Heroku, but the same steps should work for any Elixir web application, the only requirement AFAICT is that your application must boot with the mix run --no-halt command, and listen on the port specified by the $PORT env variable.

Step 0 - install the Heroku toolbelt if you don’t have it already

Instructions are here.

Step 1 - create the Heroku application

$ heroku create
Creating app... done, ⬢ glacial-waters-81278
https://glacial-waters-81278.herokuapp.com/ | https://git.heroku.com/glacial-waters-81278.git

NOTE: This automatically added the heroku remote to ./.git/config on my machine, if git push heroku master fails with an unknown remote error you’ll need to add that manually.

Step 2 - add the Elixir buildpack

We’re going to use this Elixir buildpack for Heroku.

$ heroku buildpacks:set https://github.com/HashNuke/heroku-buildpack-elixir

Step 3 - add a config file for the buildpack (optional)

Create the following elixir_buildpack.config file in your project root. Note that your versions may differ from mine. This step is not strictly necessary but I like to have the control over what versions I am running.

#./elixir_buildpack.config
# Erlang version
erlang_version=19.3

# Elixir version
elixir_version=1.4.2

# Always rebuild from scratch on every deploy?
always_rebuild=false

# A command to run right before compiling the app (after elixir, .etc)
pre_compile="pwd"

# A command to run right after compiling the app
post_compile="pwd"

# Set the path the app is run from
runtime_path=/app

Step 4 - configure your application to listen to $PORT env variable

Heroku expects your app to listen on a $PORT env variable which is randomly set.

If you don’t set this, your app will attempt to listen on a denied port and you’ll see an error in your Heroku logs that looks something like this:

2017-04-29T10:31:34.456439+00:00 app[web.1]: 10:31:34.455 [error] Failed to start Ranch listener HelloWebhook.Endpoint.HTTP in :ranch_tcp:listen([port: 80]) for reason :eacces (permission denied)

The easiest way to get your app to listen on the correct port is to modify your config/prod.exs file like so:

# ./config/prod.exs
use Mix.Config

port =
  case System.get_env("PORT") do
    port when is_binary(port) -> String.to_integer(port)
    nil -> 80 # default port
  end

config :my_app, port: port

Make sure you are starting cowboy with the correct port in your worker, e.g.

#./lib/my_app/endpoint.ex
# ...
def start_link do
  port = Application.fetch_env!(:my_app, :port)
  {:ok, _} = Plug.Adapters.Cowboy.http(__MODULE__, [], port: port)
end
# ...

Step 5 - make your first push

$ git push heroku master

If you did everything right, the push should be successful and the logs should show something like this:

2017-04-29T10:38:10.905309+00:00 heroku[web.1]: Starting process with command `mix run --no-halt`
2017-04-29T10:38:14.630637+00:00 heroku[web.1]: State changed from starting to up

And you’re done - hoorah! To open your app in the browser:

$ heroku open