Continuous Deployment of Phoenix apps to Heroku via Semaphore CI

综合编程 2017-08-03

In the last episode, we set up continuous integration for Firestorm via Semaphore . Today we'll deploy our app to Heroku , then set up Semaphore to deploy each time the tests pass on the master branch. Let's get started.


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

I've already signed up for Heroku 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. I'll be using the Heroku Elixir Buildpack .

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

heroku create firestorm-forum --buildpack ""

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

heroku buildpacks:add

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: "", 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
  # ...

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 app as well. We can configure this by creating an elixir_buildpack.config file:

vim elixir_buildpack.config

Some of these env vars are secret, so I'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

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, I'll configure the frontend to hit this API:

vim assets/config/production.js
module.exports = {
  apiBaseUrl: ""

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

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

We'll click 'Heroku':

We'll pick automatic:

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

Next, I have to paste my Heroku API key, which I'll do off-screen. Then we pick the app 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!


Today we deployed Firestorm to Heroku and set up Continuous Deployment via Semaphore. My preference with Elixir is to do a deployment to a bare machine, rather than using a PaaS such as Heroku. However, I think it's important to show this off, and honestly it's a very fast way to get Firestorm up and running. I hope you enjoyed it. See you soon!



Build a MapReduce flow in Elixir MapReduce is a common Big Data pattern for analyzing a data set concurrently. This tutorial will introduce you to Elixir and the...
deploy nodejs to heroku I've followed the getting started guide to deploy a nodejs application to heroku: ...
Lightweight dependency injection in Elixir (withou... In our last Elixir blog post, “Functional Mocks with Mox in Elixir” , we discussed how testing across module boundaries could be made easier by ...
Introduction to Metaprogramming The post belongs to NectarCommerce and Extension Framework Awareness NectarCommerce Vision ...
How does Sails JS handle push messages connected t... I've got a question about scaling a SailJS app on Heroku. The setup : SailJS Kue (Redis queueing system) Conne...

责编内容来自:Lobsters (本文源链)
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。