CI Setup with GitHub Actions for Ruby
Continuous Integration (CI) is essential for maintaining healthy Ruby projects. GitHub Actions provides a powerful, free way to automate your CI pipeline. In this guide, you’ll set up a complete CI workflow for a Ruby project.
Why GitHub Actions?
GitHub Actions integrates seamlessly with Ruby repositories. You get:
- Free minutes for public and private repositories
- Matrix builds to test across multiple Ruby versions
- Caching to speed up your workflows
- Native integration with GitHub’s ecosystem
Basic CI Workflow
Create .github/workflows/ci.yml in your repository:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rake
This workflow runs on every push and pull request to main.
Testing Multiple Ruby Versions
Use a matrix strategy to test across Ruby versions:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rake
Each Ruby version runs as a separate job. Set fail-fast: false to let all versions complete even if one fails.
Adding Linting
Include RuboCop in your CI pipeline:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Install RuboCop
run: bundle add rubocop --skip-install && bundle install
- name: Run RuboCop
run: bundle exec rubocop
Run linting in parallel with tests:
jobs:
test:
# ... test steps
lint:
# ... lint steps
ci:
needs: [test, lint]
Caching Dependencies
Speed up your workflows with caching:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
cache-version: ${{ hashFiles('Gemfile.lock') }}
The bundler-cache: true option automatically caches gems between runs.
Running Database Tests
For Rails projects, set up a database service:
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
RAILS_ENV: test
Complete Example
Here’s a production-ready workflow for a Rails application:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
fail-fast: false
matrix:
ruby-version: ['3.1', '3.2', '3.3']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Set up database
run: bin/rails db:test:prepare
- name: Run tests
run: bundle exec rake
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Run RuboCop
run: bundle exec rubocop
Best Practices
- Use latest actions versions —
ruby/setup-ruby@v1andactions/checkout@v4 - Cache bundler — Always enable
bundler-cache: true - Test multiple Ruby versions — Use matrix strategy for compatibility
- Separate concerns — Run linting and tests as separate jobs
- Fail-fast carefully — Use
fail-fast: falsefor comprehensive feedback