Set Up GitLab CI for Rails Applications
It is hard to go back, once you integrate CI/CD flow into your workflow. Luckily, tools like GitLab are great, as they provide not only a nice GIT hosting platform, but a robust continues integration and delivery platform.
Let’s setup our .gitlab-ci.yml
file, so it meets our needs.
First steps
We will use the official Ruby docker image as a start and add PostgreSQL service. It is good idea to cache our dependencies as this will remove the need to download them if there are no changes.
image: ruby:2.4.3
cache:
paths:
- vendor/bundle
- node_modules
services:
- postgres:10.1
variables:
BUNDLE_PATH: vendor/bundle
DISABLE_SPRING: 1
DB_HOST: postgres
Before script
The before_script
is the place, where you can put your setup steps that will run before every stage. Here we will put the installation of additional software that we need and the initial setup of the project, like running migration, compiling assets, etc.
before_script:
# Install node and some other deps
- curl -sL https://deb.nodesource.com/setup_8.x | bash -
- apt-get update -yq
- apt-get install -y apt-transport-https build-essential cmake nodejs python-software-properties software-properties-common unzip
# Install yarn
- wget -q -O - https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
- echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
- apt-get update -yq
- apt-get install -y yarn# Project setup
# Check if the dependencies are ok, if not install what is missing
- bundle check || bundle install --jobs $(nproc)
- yarn install# database.yml.ci file contains the configurations for the CI
# server, so let's copy to the configuration file
- cp config/database.yml.ci config/database.yml- bundle exec rails db:create RAILS_ENV=test
- bundle exec rails db:schema:load RAILS_ENV=test
- bundle exec webpack
Take a note that we copy our CI specific database configuration file, which contains the correct values for the runner. It should have the following content:
default: &default
adapter: postgresql
encoding: unicode
username: postgres
password: postgres
host: <%= ENV['DB_HOST'] %>
pool: 5
database: ci_db
Defining the stages
The stages
block of the config file defines all the stages of the build process of the app. We, at Evermore, usually go with three: test, lint and deploy.
stages:
- test
- lint
- deploy
Test stage
GitLab allows to have as many task per stage as you like, which makes perfect sense for our unit and system tests. But to be able to run tests using headless Chrome, we need to install it and install chromedriver
as well. As this is kinda expensive task, we only do it when we actually need it.
Tests:
stage: test
script:
- bundle exec rails test -dSystem Tests:
stage: test
script:
- ./bin/setup_chrome
- bundle exec rails test:system
Here is the ./bin/setup_chrome
file
#!/bin/bashset -e# Install Chrome
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
apt-get update -yqqq
apt-get install -y google-chrome-stable > /dev/null 2>&1
sed -i 's/"$@"/--no-sandbox "$@"/g' /opt/google/chrome/google-chrome# Install chromedriver
wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/2.35/chromedriver_linux64.zip
unzip /tmp/chromedriver.zip chromedriver -d /usr/bin/
rm /tmp/chromedriver.zip
chmod ugo+rx /usr/bin/chromedriver
Lint stage
At Evermore we have a coding style guide and one thing that we like to do is to automate the review process of lint violations. It is always a better idea if they come from a machine than a person :) To do this we use this awesome gem called pronto. It has various runners and rubocop is one of them.
Pronto:
stage: lint
allow_failure: true
except:
- master
script:
- bundle exec pronto run -f gitlab -c origin/master
As this is not curtail part of our build, we allow failures for this task. And, of course, there is no need to run this task on the master branch, because we want to lint only the change in a pull (merge) request.
Deploy stage
Deploy stage can be different depending on the needs. We usually use Heroku and have two environments — staging and production. We deploy to staging on successful build on branches (pull requests), so it is easy to review and deploy the master branch to production.
The easiest way to deploy your app to Heroku, is using the dpl
package. You just need to install it and setup an API key.
Do not forget to add `$HEROKU_API_KEY` to your secret variables in GitLab.
Deploy Production:
stage: deploy
retry: 2
only:
- master
script:
- ./bin/setup_heroku
- dpl --provider=heroku --app=awesome-app --api-key=$HEROKU_API_KEY
- heroku run rake db:migrate --exit-code --app awesome-appDeploy Staging:
stage: deploy
allow_failure: true
retry: 2
except:
- master
script:
- ./bin/setup_heroku
- dpl --provider=heroku --app=awesome-app-staging --api-key=$HEROKU_API_KEY
- heroku run rake db:migrate --exit-code --app awesome-app-staging
- heroku run rake db:seed --exit-code --app awesome-app-staging
Here is the ./bin/setup_heroku
file
#!/bin/bashset -eapt-get update -yq
apt-get install apt-transport-https software-properties-common python-software-properties -y
add-apt-repository "deb https://cli-assets.heroku.com/branches/stable/apt ./"
curl -L https://cli-assets.heroku.com/apt/release.key | apt-key add -
apt-get update -yq
apt-get install heroku -y
gem install dpl
We install Heroku’s CLI tool, so we can run tasks like migrations, seeding the database and so on.
And we are done
Of course, we can go a step further and create a custom docker image. This way we will cache some of the setup steps and speed up the builds. Strangely enough, I was not able to find an image that fits my needs for this specific project, so I created one — https://hub.docker.com/r/mupkoo/rails-ci/. Give it a try if you like.
Here is the full script. Tweak it and make it yours. Good luck!