---
title: Setting up CircleCI 2.0 for Rails
teaser: 'CircleCI 2.0 brings some great new features, but it''s not so straightforward
  to get started with. Let''s walk through how to get going.

  '
tags: rails,ci,circleci
author: Nick Charlton
published_on: 2018-01-03
---

In [July of 2017, CircleCI released their new platform, 2.0][announcement].
It’s much more powerful and flexible, but it's also more complex to get up
and running with for Rails apps. Let’s walk through it…

## A Base Configuration

The CircleCI configuration file is now in `.circleci/config.yml`. Initially,
it should start looking something like this:

```yaml
---
version: 2
jobs:
    build:
        working_directory: ~/your-app-name
    steps:
        - checkout
```

This doesn’t do all that much (and wouldn’t work on its own). But it’s the
basic shell of the config file we’ll need. These parts will be there
throughout.

## Choosing a base Docker Image

For our use case, the best choice is the Docker executor, which will compose
the environment from a set of Docker images. [CircleCI provide set of
pre-built images that we’ll use][images]. These cover lots of different Ruby
versions, with variations for common requirements like a browser and/or
JavaScript.

These variations can be specified with tags on the Docker image, like so:

```yaml
---
version: 2
jobs:
    build:
        working_directory: ~/your-app-name
        docker:
            - image: circleci/ruby:2.4.1
            environment:
                RAILS_ENV: test
    steps:
        - checkout

      # Setup the environment
      - run: cp .sample.env .env

      # Run the tests
      - run: bundle exec rake
```

This adds the Ruby image for `2.4.1`. If we need a browser, we might use the
`2.4.1-browsers` tag (for Selenium, for example). If we have JavaScript tests,
we might use `2.4.1-node` and if we have both we might want
`2.4.1-node-browsers`.

Now we have an environment we can run in, we’ll also go about including what
we need to run the tests. Yours might vary here.

## Services

A Rails app is rarely without a database and we can introduce that with another
Docker image. We’ll use PostgreSQL here, but others would work in the same
manner:

```yaml
---
version: 2
jobs:
  build:
    working_directory: ~/your-app-name
    docker:
      - image: circleci/ruby:2.4.1
        environment:
          PGHOST: localhost
          PGUSER: your-app-name
          RAILS_ENV: test
      - image: postgres:9.5
        environment:
          POSTGRES_USER: your-app-name
          POSTGRES_DB: your-app-name_test
          POSTGRES_PASSWORD: ""
    steps:
        - checkout

      # Setup the environment
      - run: cp .sample.env .env

      # Setup the database
      - run: bundle exec rake db:setup

      # Run the tests
      - run: bundle exec rake
```

This adds a `postgres` image, but also adds some environment configuration.
We provide similar values to what we’re using in `config/database.yml`.
Specifying `POSTGRES_DB` on the `postgres` image ensures that the correct
database is created.

Additionally here, we add the `rake` commands for configuring our database.

Our configuration file should now be successfully running tests. It was at
this point that I found I needed to iterate on the base Docker image to make
sure I was covering all of my dependencies and most runs were passing
successfully.

## Avoiding race conditions

One problem we should take care of though is race conditions to do with how
the additional services come up. It’s possible that Postgres might take
slightly longer to be available and so our tests might start running …and
then all fail.

To solve this, we can use `dockerize` to wait for the appropriate port to be
available. Add this as a run step:

```yaml
- run: dockerize -wait tcp://localhost:5432 -timeout 1m
```

## Caching dependencies

Running `bundle` for each test run can be slow and is easy to cache. To give
us a little speed improvement, we can do something like this:

```yaml
# Restore Cached Dependencies
- type: cache-restore
  name: Restore bundle cache
  key: your-app-name-{{ checksum "Gemfile.lock" }}

# Bundle install dependencies
- run: bundle install --path vendor/bundle

# Cache Dependencies
- type: cache-save
  name: Store bundle cache
  key: your-app-name-{{ checksum "Gemfile.lock" }}
  paths:
    - vendor/bundle
```

This will try and restore any cached dependencies using a checksum based on
your lock file. So if you add more things and it’s invalid it won’t try and
use something which isn’t useful. Installing dependencies to a specific path
allows us to use `cache-save` to save that particular directory.

## Putting it all together

All of these come together to look like this:

```yaml
---
version: 2
jobs:
  build:
    working_directory: ~/your-app-name
    docker:
      - image: circleci/ruby:2.4.1
        environment:
          PGHOST: localhost
          PGUSER: your-app-name
          RAILS_ENV: test
      - image: postgres:9.5
        environment:
          POSTGRES_USER: your-app-name
          POSTGRES_DB: your-app-name_test
          POSTGRES_PASSWORD: ""
    steps:
      - checkout

      # Restore Cached Dependencies
      - type: cache-restore
        name: Restore bundle cache
        key: your-app-name-{{ checksum "Gemfile.lock" }}

      # Bundle install dependencies
      - run: bundle install --path vendor/bundle

      # Cache Dependencies
      - type: cache-save
        name: Store bundle cache
        key: your-app-name-{{ checksum "Gemfile.lock" }}
        paths:
          - vendor/bundle

      # Wait for DB
      - run: dockerize -wait tcp://localhost:5432 -timeout 1m

      # Setup the environment
      - run: cp .sample.env .env

      # Setup the database
      - run: bundle exec rake db:setup

      # Run the tests
      - run: bundle exec rake
```

You can see this configuration in action on [administrate][].

[announcement]: https://circleci.com/blog/launching-today-circleci-2-0-reaches-general-availability/
[images]: https://hub.docker.com/r/circleci/ruby/tags/
[administrate]: https://github.com/thoughtbot/administrate/pull/1031
