In today's episode, we're going to deploy the BEAM Toolbox to Heroku using "heroku-buildpack-elixir" from HashNuke. We'll also take a quick diversion at the start to bring our application up to date with the latest version of Phoenix, which introduces a nice configuration layer. Let's get started.

Project

I've tagged the beam_toolbox repository with before_episode_067 if you want to follow along.

Alright, so the first thing we're going to do is update our version of phoenix.

mix deps.update phoenix

Adding Configuration

The episode's resources section links to one of the more interesting recent additions to Phoenix, which is the ExConf project. It provides a nice DSL for managing application configuration - in this case, for specifying that the development environment should run on port 4000, but the production environment should look for the PORT environment variable, which is populated by Heroku and is necessary for your application to be visible to the outside world.

In order to use ExConf with Phoenix, we'll need to define a BeamToolbox.Config module, and then a module underneath it for each of our three environments: dev, prod, and test.

Start off by making the lib/beam_toolbox/config directory:

mkdir lib/beam_toolbox/config

Then open up lib/beam_toolbox/config/config.ex:

defmodule BeamToolbox.Config do
  use Phoenix.Config.App

  config :router, port: System.get_env("PORT")
  config :plugs, code_reload: false
  config :logger, level: :error
end

This is our base configuration to be used for fallback. Next, we'll define our dev environment's config. Open up lib/beam_toolbox/config/dev.ex:

defmodule BeamToolbox.Config.Dev do
  use BeamToolbox.Config

  config :router, port: System.get_env("PORT") || "4000",
                  ssl: false
  config :plugs, code_reload: true
  config :logger, level: :debug
end

Here, we've defined the port we'll use in dev - either the environment variable PORT, or we'll fall back to 4000. We've also enabled phoenix's code reloading in development, which should speed up our development cycle. Next, open up lib/beam_toolbox/config/prod.ex:

defmodule BeamToolbox.Config.Prod do
  use BeamToolbox.Config

  config :router, port: System.get_env("PORT")
  config :plugs, code_reload: false
  config :logger, level: :error
end

Here, we require the PORT environment variable. Finally, open up lib/beam_toolbox/config/test.ex:

defmodule BeamToolbox.Config.Test do
  use BeamToolbox.Config

  config :router, port: 4001,
                  ssl: false
  config :plugs, code_reload: true
  config :logger, level: :debug
end

Here, we'll run it on port 4001 when testing. Phoenix already knows how to look in this config to find these configuration variables, but ExConf is entirely usable outside of Phoenix, and it's something I've missed in Elixir until now. Kudos to Chris McCord and anyone else involved for building it. I know Jose gave some suggestions on how it should work early on as well.

Finally, our router is hardcoded to specify port 4000 presently. Let's remove that. Open up lib/beam_toolbox/router.ex

use Phoenix.Router

Adding Supervision

Something else we've done incorrectly so far is we aren't really supervising anything. We want to make it so that when you run our application, it Just Works, and if any parts of it die they're restarted appropriately. To deal with this, we'll move a couple of things from lib/beam_toolbox.ex into the application's supervisor. (Open up lib/beam_toolbox.ex and move stuff)

    # Apparently I can't start an HTTPotion in a supervisor because it returns
    # something that isn't compatible with being a supervision child, so we'll just
    # start it and fail to supervise it...
    BeamToolbox.GitHub.start()

    # Now we'll add our router and cadfaerl to the supervision tree
    children = [
      worker(:cadfaerl, [:github, 2000]),
      worker(BeamToolbox.Router, [], function: :start)
    ]

Alright, so now when our app starts, it will start our router, which starts the BeamToolbox.GitHub HTTPotion-based module, and then supervises our cache and our router. This concludes all the preparation work, and we can move on to deploying to heroku.

Deploying to Heroku

So, it's almost sad to say, but deploying to Heroku isn't even hard work. HashNuke built a heroku buildpack that takes care of everything for us. Let's create a new project on heroku, and tell it to use this buildpack:

heroku create --buildpack "https://github.com/HashNuke/heroku-buildpack-elixir.git"

Alright, so now our application is ready on heroku. Next, we'll need to configure the buildpack. This is done with an elixir_buildpack.config file in the root of the project. Create that file, and put this in there:

# Erlang version
erlang_version=17.0

# Elixir version (here I'm just specifying a commit because if you specify
# master it's rebuilt each push)
elixir_version=(commit 0883c753356a08)

# Rebar version
rebar_version=(tag 2.2.0)

# Do dependencies have to be built from scratch on every deploy?
always_build_deps=false

Alright, that's really all it takes to configure this buildpack. Next, the only real thing to take care of is adding a Procfile so heroku knows what to run for the web processes. Open up a new file called Procfile:

web: MIX_ENV=prod mix run --no-halt

This just sets our mix environment to prod on heroku, then runs our app and tells it not to halt after starting up. Now, we just need to commit all of this:

git add .
git commit -m"Upgrade phoenix and add heroku deployment"
git push origin episode_067
git push heroku episode_067:master

Now heroku will use their binary for erlang 17.0, build elixir this one time for us, and then compile our application and deploy the slug. Once it's all done, we should be able to visit in the browser, and it'll be running:

(visit the url that heroku gives us, plus /pages/home. Then click Amrita.)

Summary

Alright, so that's all there is to it. Most of our time was spent upgrading Phoenix so that we could have heroku give us the port - this buildpack makes actually deploying an elixir application to heroku trivial.

This is really exciting, and now we can start making beam toolbox a bit more functional. See you soon!

Resources