Skip to Content
Course content

234: CI/CD Pipelines for Ruby Projects

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was mentoring a developer who had just finished a massive feature for a Rails-based e-commerce engine. He’d spent three days polishing the code, ran the tests locally, and felt great. He merged his branch into main and triggered the deploy. Ten minutes later, the site went down. The culprit? A missing environment variable in the production environment that he'd accidentally hardcoded in his local .env file. He didn't know it was missing because his local machine "just worked," and we had no automated way to verify the deployment readiness before it hit the live server. It was a stressful afternoon of rolling back commits and apologizing to stakeholders.

That's the nightmare scenario that CI/CD is designed to kill. Continuous Integration (CI) is the practice of automatically building and testing your code every time you push a change. Continuous Deployment (CD) takes it a step further by automatically pushing those verified changes into production. In the Ruby ecosystem, we have incredible tools that make this seamless, but the magic doesn't happen by accident; you have to define the pipeline.

Orchestrating the Workflow with GitHub Actions

While there are plenty of tools like CircleCI or Jenkins, GitHub Actions has become the go-to for most Ruby projects because it lives right next to your code. To get started, you create a YAML file in your repository under .github/workflows/. This file is essentially a script that tells GitHub: "Whenever someone pushes to the main branch, spin up a Linux virtual machine, install Ruby, and run these specific commands."

I always recommend starting with a "fail-fast" approach. You don't want to wait ten minutes for a full integration test suite to run if you forgot a comma in a configuration file. Your pipeline should be ordered by speed and volatility. First, install dependencies, then run your linter, and finally, run your test suite. If any of these steps fail, the pipeline stops immediately, and the merge is blocked. It's a digital safety net that ensures no one—including you—accidentally breaks the build.

# A snippet of what your workflow might look like
name: Ruby CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:13
        ports: ["5432:5432"]
    steps:
      - uses: actions/checkout@v3
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true # This is a lifesaver for speed
      - name: Run RuboCop
        run: bundle exec rubocop
      - name: Run RSpec
        run: bundle exec rspec

Hardening the Pipeline with Linting and Testing

You've already learned how to write RSpec tests, but in a CI/CD context, those tests serve a different purpose. Locally, tests are for development; in CI, they are a "quality gate." I've seen teams ignore RuboCop locally, only to have their CI pipeline reject 50 different PRs because of trailing whitespace. To avoid this friction, make sure your CI configuration matches your local environment exactly.

One specific Ruby quirk to watch out for is the Gemfile.lock. Your CI pipeline should use bundle install to ensure it's using the exact versions of gems specified in your lockfile. If you use bundle update inside a pipeline, you're introducing non-deterministic behavior—essentially gambling that a new version of a dependency won't break your app in the middle of a deployment. Stick to the lockfile.

Bridging the Gap to Deployment

Once the CI phase passes, the "CD" part kicks in. Depending on where you host—whether it's Heroku, AWS, or a private VPS—the deployment step usually involves pushing the code to a production server or updating a container image. The key here is the use of "Secrets."

Remember my story about the missing environment variable? You solve that by using GitHub Secrets. Instead of hardcoding API keys or database URLs, you store them in the GitHub repository settings and inject them into the pipeline as environment variables. This ensures that your production environment is configured correctly and securely without ever committing sensitive data to version control. If the secret is missing, the deployment step fails, and you find out before the users do.




📋 Practical Task

Exercise: Building a Quality Gate Pipeline for a Ruby Project

Your task is to create a complete GitHub Actions workflow file for a Ruby project that enforces code quality and test success before allowing a merge. Imagine you are working on a project that uses RuboCop for linting and RSpec for testing.

Create a file named .github/workflows/ci.yml that meets the following requirements:

  • Triggers: The workflow must run on every push to the main branch and every pull_request.
  • Environment: Use the ubuntu-latest runner and the ruby/setup-ruby action to install Ruby 3.2.
  • Caching: Enable bundler-cache: true to ensure the pipeline doesn't waste time reinstalling gems on every run.
  • The Sequence:
    1. Checkout the code.
    2. Install dependencies via Bundler.
    3. Execute bundle exec rubocop.
    4. Execute bundle exec rspec.

Ensure that if RuboCop fails, the RSpec tests do not run. This simulates a real-world "fail-fast" pipeline.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.