CircleCI Elixir setup

elixirphoenixCircleCIDockercontinuous integration

I’ve recently moved my projects from another cloud CI provider to CircleCI. I was familiar with CircleCI v1 from a previous project I was working on and decided to give v2 a try. The process of transferring the projects was a smooth one and I’m quite happy with the results and the speed of CI builds. Many of my projects are based on Elixir and Phoenix and this one is no exception. My requirements were to be able to build the project, have a Postgresql database available and be able to run Elixir based ExUnit tests as well as acceptance tests using JavaScript.

Workflow

The builds can be configured in a couple of different ways. The first one is to create a stand-alone job, which can be one of several types but in this case, we are using build.

There is a setup guide on the Circle CI website and there are other tutorials too which helped setting everything up.

Configuration

CircleCI 2 configuration is based solely on the circleci YAML file (.circleci/config.yml). This is good as your CI pipeline will be treated as code and you won’t run into the problem where the person who setup the Jenkins instance two years ago and knows how it works has left the company. Jenkins configuration can of course also be treated as code but with many of the cloud CI providers there is no other option.

Docker images

CircleCI uses Docker images to make it easier to run your own code as well as services the application depends on. For Phoenix applications, Postgresql is the most often used database and adding it in the CircleCI config file is easy. One only needs to specify the Docker image to use and any environment variables that are required for the operation of the service. CircleCI uses Docker to run the image and the service with possibly extra configuration that has been provided.

In this example, we use the Alpine Linux based Postgres 10 image. The environment variables define the values that can be used when connecting to the DB later on. Alpine Linux is a lightweight Docker image perfect for running services without too many dependencies and keeping the Docker images small for quick downloads.

- image: circleci/postgres:10-alpine
  environment:
    POSTGRES_USER: postgres
    POSTGRES_DB: app_test
    POSTGRES_PASSWORD: app_pw

The Elixir app is also going to be run in a Docker container. A Phoenix app usually contains some JavaScript (and other static assets), which needs to be installed by npm/yarn and handled by Webpack. If the front-end needs to be tested in the same box, having a browser available in the Docker image makes things easier as one doesn’t need to be installed separately. CircleCI has made an image available that suits these needs perfectly, i.e. elixir:node-browsers. It’s pulled in in the next config section shown below:

- image: circleci/elixir:1.8.1-node-browsers
  environment:
    MIX_ENV: test
    POSTGRES_USER: postgres
    POSTGRES_DB: app_test
    POSTGRES_PASSWORD: app_pw

When the application runs, it needs to connect to the DB and it can do just by with the help of the DB name, user and password configured above. The keys/values under the environment key are set as part of the application environment and the application can pull in these values by defining the test configuration (config/test.exs) in the following way:

username: System.get_env("POSTGRES_USER") || "<local fallback>",
password: System.get_env("POSTGRES_PASSWORD") || "<local fallback>",
database: System.get_env("POSTGRES_DB") || "<local fallback>",

The local fallback values are options and are used when the environment variable is not set. I typically use static values during development on the local machine.

The full configuration is available in this gist.

Caching

CircleCI supports caching to make it possible to reuse specific directories from a build. Anything to do with npm can be quite slow and being able to cache the assets is going to significantly speed up the builds. The configuration shown in the gist above has caching configured for both npm packages and Elixir dependencies. Much of the caching config is based on this blog post by Joe Ellis.

Summary

When adding a project to CircleCI, the UI suggests to create and push the config file to the repo and start building. The beauty of having a Yaml based CI configuration is that it’s easy as copying a file to get started.