Continuous deployment of an elixir phoenix application to heroku with semaphore

Continuous Deployment of an Elixir Phoenix Application to Heroku with Semaphore

Learn how to deploy an Elixir Phoenix app to Heroku and set up Semaphore to continuously deploy each time tests pass on the master branch.

Speed up your Elixir tests and deployment on Semaphore.

Test and deploy faster

This tutorial was initially published on DailyDrip. Read the original post here.

Continuous Deployment to Heroku

In the previous tutorial, we set up continuous integration for Firestorm via Semaphore. In this tutorial, we'll deploy our application to Heroku, and then set up Semaphore to deploy each time the tests pass on the master branch. Let's get started.

Project

We're starting with the dailydrip/firestorm repo tagged before this episode.

We've already signed up for Heroku and Semaphore and installed the Heroku Toolbelt, which is Heroku's CLI.

Heroku created the concept of buildpacks, which are a way to tell Heroku how to set up and run your application. We'll be using the Heroku Elixir Buildpack.

We'll create an app named firestorm-forum, specifying the buildpack:

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

We need to add another buildpack, to handle the static compilation for our frontend assets.

heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git

We need to tweak our configuration a little to fetch some environment variables from Heroku:

vim config/prod.exs
# We'll enable SSL and force its use, and fetch the SECRET_KEY_BASE environment
# variable.
config :firestorm_web, FirestormWeb.Web.Endpoint,
  # ...
  url: [scheme: "https", host: "firestorm-forum.herokuapp.com", port: 443],
  force_ssl: [rewrite_on: [:x_forwarded_proto]],
  secret_key_base: System.get_env("SECRET_KEY_BASE")
# ...
# We'll use the POOL_SIZE var and let Heroku tell us how to talk to our database
config :firestorm_web, FirestormWeb.Repo,
  adapter: Ecto.Adapters.Postgres,
  url: System.get_env("DATABASE_URL"),
  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
  ssl: true
# and we don't need a prod secrets file so we can remove that

We're not using Phoenix Channels yet, but we will in the future. We need to reduce the timeout time for the websocket transport so that connections are closed before Heroku's 55 second timeout:

vim lib/firestorm_web/web/channels/user_socket.ex
defmodule FirestormWeb.Web.UserSocket do
  # ...
  transport :websocket, Phoenix.Transports.WebSocket,
    timeout: 45_000
  # ...
end

Finally, we'll create a Procfile. This is what Heroku uses to determine how to start your application:

vim Procfile
web: MIX_ENV=prod mix phx.server

Now, let's create our database on Heroku:

heroku addons:create heroku-postgresql:hobby-dev

And we'll configure its pool size:

heroku config:set POOL_SIZE=18

We get 20 connections to our database on the hobby tier, so we'll configure it to be slightly below that number so we have a couple of spare connections to use for things like migrations and mix tasks.

When we do run a mix task, we can limit the number of connections it attempts to create to make sure we don't exceed the headroom:

# example:
# heroku run "POOL_SIZE=2 mix ecto.migrate"

Let's create the secret key and set our Heroku environment variable:

mix phoenix.gen.secret
# copy its output
heroku config:set SECRET_KEY_BASE="the_previous_output"

We have a few more environment variables that we need to make available to our application as well. We can configure this by creating an elixir_buildpack.config file:

vim elixir_buildpack.config
config_vars_to_export=(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_S3_BUCKET AWS_S3_REGION GITHUB_CLIENT_ID GITHUB_CLIENT_SECRET)

Some of these env vars are secret, so we'll set them off-screen. Now, we're ready to deploy. Let's add the files we changed and push it up to our repo:

git checkout -b feature/episode_010.3
git add config/prod.exs lib/firestorm_web/web/channels/user_socket.ex elixir_buildpack.config
git commit -m"Add Heroku configuration"
git push origin feature/episode_010.3

Now we can deploy to Heroku. Normally you would just push master up to Heroku, but we'll push this feature branch to Heroku's master branch instead:

git push heroku feature/episode_010.3:master

On a Phoenix application that uses Brunch, this would be sufficient. We're using webpack though, so there's a little more work that has to go into it. To get Heroku to build our assets with webpack, we can create a compile script that the buildpack will use:

vim compile
#!/bin/bash

NODE_ENV=production ./node_modules/.bin/webpack -p
cd ..
mix phx.digest
chmod +x compile
git add .
git commit -m"Add custom compile script to handle webpack"
git push origin feature/episode_010.3
git push heroku feature/episode_010.3:master

If we push this, it's still not quite enough - there's an error when uglify tries to run. Let's add a .babelrc for our assets:

vim assets/.babelrc
{
  "presets": [
    ["es2015", { "modules": false }]
  ]
}
git add .
git commit -m"Add babelrc for assets"
git push origin feature/episode_010.3
git push heroku feature/episode_010.3:master

With this, everything pushes up and builds successfully. If we try to visit the site, it won't work though. We can check the logs to find out why:

heroku logs

We haven't run our migrations. Let's do that:

heroku run "POOL_SIZE=2 mix ecto.migrate"
heroku ps:restart

Now if we visit the app, it works. Mostly. We still haven't setup the frontend to hit the deployed URL, and we'd really like to set up continuous deployment so it gets deployed each time we push.

First, we'll configure the frontend to hit this API:

vim assets/config/production.js
module.exports = {
  apiBaseUrl: "https://firestorm-forum.herokuapp.com/api/v1/"
};

Before we push this, we'll set up semaphore for continuous deployment.

First, we'll click Set up deployment on semaphore for the project:

Set Up Deployment on Semaphore

We'll click 'Heroku':

Select Deploy with Heroku on Semaphore

We'll pick automatic deployment:

Deploy Automatically using Semaphore

Then we'll pick the branch we want to deploy from.

Next, we have to paste my Heroku API key. Then, we pick the application to deploy and complete setup.

We can also modify the deployment steps to add a migration step after each deploy:

heroku run --exit-code -- MIX_ENV=prod POOL_SIZE=2 mix ecto.migrate

Now we can push the js config change up, just to our github this time - we'll let Semaphore handle the Heroku deployment.

git add .
git commit -m"Set up js production url"
git push origin feature/episode_010.3

Once the tests pass, it will deploy to Heroku for us. And that's it!

Summary

Today we deployed Firestorm to Heroku and set up Continuous Deployment via Semaphore.

If you'd like to sign up for Semaphore, you can use the coupon code DAILYDRIP30 for a 30% discount for three months. The coupon expires on August 31, 2017.

Feel free to leave any comments or questions you may have in the comment section below.

Bbed7ad0067f746ff1d286d82c92495c
Josh Adams

Josh was a CTO for over a decade, building software for small businesses, startups, and government agencies. In the last four years, he realized that functional programming was in fact amazing, and has been eager to help people build software better.

on this tutorial so far.
User deleted author {{comment.createdAt}}

Edited on {{comment.updatedAt}}

Cancel

Sign In You must be logged in to comment.