Phoenix |> CircleCI |> Heroku deployment
One thing that is often missing when learning a new language or framework is how to do all the “ops” stuff associated with it. Whilst working through the example application in Programming Phoenix, I wanted to have a deployment pipeline setup as if I was working on a real-life application. Learning how to go from code you write to an application someone can use is equally important, as this is often an area that can suck up time in a project. I wanted to learn more about the deployment side and how to setup a continuous integration server that would run tests and deploy code somewhere. I chose CircleCI and Heroku, as they both have very liberal free accounts that give me everything I need.
NOTE: I’m going to presume a default
mix phoenix.new circle_heroku
application for the rest of this post
CircleCI
CircleCi is a cloud-based “continuous integration and delivery service”. Having recently been introduced to CircleCI in my previous job I knew I wanted to use it again, as it offered an easy setup, great user experience, easy integrations and good out of the box support for many languages and platforms.
CircleCI signup
First of all, we’re going to need a CircleCI account. Sign-up is simply a case of linking your GitHub or Bitbucket account. Once you’ve done that, you can go to your dashboard and click on the Projects tab to add a new project:
In the above screen shot, I’ve linked my GitHub account with CircleCI. After clicking on your GitHub account you should then see a list of your projects in the main window. Click the green Build Project button to add your project to CircleCI. This will begin to build the project. This build will fail :)
Setting up Phoenix for CircleCI
So I just said CircleCI has great support out of the box for many languages right? Unfortunately, Elixir isn’t one of them. Thankfully, the community has already plugged the gap and worked out how to build Elixir apps on CircleCI. But lets take a step back for a moment and think about what we want CircleCI to do for us:
- Compile our code
- Run our tests
- Deploy to Heroku
You configure CircleCI via a circle.yml
file. You can read the docs here. The bit we’re interested in is the dependencies
section. In order to compile our code, we’re going to need Erlang and Elixir, and any dependencies our application needs. Create a circle.yml
file in the root of your project and add the following (taken from the thread linked to above):
Here we setup our basic machine environment and use the dependencies/pre
section to install Erlang and Elixir by way of asdf
, an extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more. Once installed, we run mix deps.get
to install our dependencies. Doing all this takes a long time, so we’re also caching some directories to improve the build times. Subsequent builds should be much quicker than the first.
Running tests
We now need to think about running the tests. A default Phoenix project uses Postgres in tests, so we’re going to need to get Postgres configured on CircleCI. Thankfully, it is installed by default - we just need to create our database and user account. CircleCI has a database
setting for this:
Here we’re adding the postgresql to our machine/services
configuration and overriding the database
configuration to create our test database and an account we will use to run the tests. Next we need to actually run the tests. Again, CircleCI provides a config setting for us to override:
You might have noticed that the mix environment is set to “ci”. This allows us to separate out running our tests locally and running on CircleCI. We need a new ci.exs
config file for our application inside our config
folder:
This is basically a straight copy from the test.exs
config file, with the Postgres settings configured to use our account details defined in out circle.yml
file. Our full circle.yml
file should now look like this:
Now we have all the pieces in place we can commit our changes which should re-trigger the build. BOOM!:
Hmmm.. that didn’t work. So what’s going on? If we look in our mix.ex
file we will see that the test command does an ecto.create
and an ecto.migrate
before running the tests:
Unfortunately, because we do not yet have any migrations, the migrations directory is empty, so does not get committed to source control. We can fix this by adding a temp.txt file to our priv/repo/migrations directory to have it picked up by git when we do a commit (a bit hacky, I know). After adding this and pushing to re-trigger the build (note we can also test locally by running MIX_ENV=ci mix test
) , we get another error “CircleHeroku.ConnCase is not loaded and could not be found”:
So it looks like we are missing ConnCase
. This is defined in test/support/conn_case.ex
, and the file definitely exists and is in source control, so it’s not a problem with the file being missing. So… why is it missing?
Per-environment compilations
If we again look at our mix.exs
file, we can see a section that lists which paths to compile per environment:
Notice that only the :test
environment compiles our test/support
folder, which is where ConnCase
lives. As we are not running as the test
environment (we are running as ci)
our test/support/conn_case.ex
does not get compiled, hence the error. We need to tell Elixir to compile the code in test/support
when running as ci
:
After committing and re-building, our project should now build successfully. To recap, we have completed our first two steps of installing all our dependencies and running our tests. There’s one last thing to do though to better integrate our tests with CircleCI.
Test reporting
Currently, if one of our tests fails, our build will fail, but we get no visual feedback as to which test failed in CircleCI :
In order to find out what failed, we have to dig into the individual build step and look at the console output to see what failed. CircleCI can collect test metadata as long as we output in JUnit XML syntax and write the XML files to a subdirectory under $CIRCLE_TEST_REPORTS
(for example $CIRCLE_TEST_REPORTS/reports
). This will then be displayed in the Test Summary tab.
Fortunately, there is already a plugin for ExUnit called junit-formatter that does exactly this. We need to add this as a dependency in mix.exs
:
and update our test_helper.exs
file to use the JUnitFormatter:
Now running mix test
will generated a test-junit-report.xml
file in _build/<mix_env>/lib/<app_name>/
. CircleCI however, expects this file to be in a $CIRCLE_TEST_REPORTS
folder, so we need to update our circle.yml
file to copy this file there after running our tests:
Now when we get a failing test, CircleCI tells us which one:
Hurray!
Deploying to Heroku
The last thing to do is to deploy our app to Heroku. The good news here is that there is an excellent guide on the Phoenix website. Go and follow that setup and make sure you can deploy to Heroku from your local terminal / cmd before continuing here (don’t forget to run heroku run "POOL_SIZE=2 mix ecto.migrate”
after your first deployment to setup Postgres).
Next, there is another excellent guide for configuring CircleCI with Heroku on the CircleCI website. Follow the first three steps — generate the API key, associated a user with your project, and update your circle.yml
file:
That’s it! We should now have everything in place so that whenever we commit code to GitHub, CircleCI will build our project, run our tests and push our code to Heroku! Visit your Heroku URL and you should see the familiar Phoenix start page:
To test it all works, make a change to web/templates/page/index.html.eex
, commit and it should go through the build process and after a few minutes appear on your Heroku site:
It works!
Thanks / Credits / Source code
Big thanks to everyone who solved the CircleCI Elixir integration, the official Phoenix guide to Heroku and of course CircleCI and Heroku for providing great free tiers that make this possible!
The full source code for this example app can be found here