DEV Community

Germán Alberto Gimenez Silva
Germán Alberto Gimenez Silva

Posted on • Originally published at rubystacknews.com on

Mastering CI Across GitHub, GitLab, Jenkins & CircleCI — Without Losing Your Sanity

May 15, 2025

CI isn’t just a nice-to-have—it’s your early warning system against code chaos. Think of it as the oracular gatekeeper of your project: it may come with some setup costs, but those are nothing compared to the cost of bugs slipping through unnoticed. With the right CI strategy, we don’t just prevent our codebase from degrading—we actively guide it toward better quality, cleaner structure, and faster feedback. In this article, we’ll walk through how to configure CI across multiple platforms, with patterns you can reuse like building blocks (and yes, a couple of developer jokes along the way—because we’ve all had CI pipelines that felt like they were laughing at us).


🚀 Want to Set Up CI and Boost Code Quality?

Looking to improve your development workflow, catch issues early, and keep your codebase clean and maintainable?

👉


🔧 The Common CI Recipe

Let’s start with the shared ingredients — the “base dough,” if you will:

  • A custom Docker image (with love, probably from Docker Hub)
  • PostgreSQL and Redis services
  • Environment variables like RAILS_ENV, SESSION_EXP_MINUTES, and MAILER_SENDER
  • Database setup using db:create db:migrate
  • RSpec tests , because what’s more satisfying than green dots?

Now let’s slice it platform by platform:


🟦 1. GitHub Actions

📄 File: .github/workflows/ci.yml


name: CI
on: [pull_request]

jobs:
  test:
    container:
      image: [IMAGE]:[VERSION]
      credentials:
        username: ${{ secrets.DOCKER_HUB_USERNAME }}
        password: ${{ secrets.DOCKER_HUB_PASSWORD }}
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: pipeline
        ports: ["5432:5432"]
      redis:
        image: redis
        ports: ["6379:6379"]
    env:
      RAILS_ENV: test
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: pipeline
      SESSION_EXP_MINUTES: 30
      UI_HOST_URL: http://react-app
      MAILER_SENDER: [email protected]
    steps:
      - name: Configure hosts and dependencies
        run: |
          echo "127.0.0.1 postgres" >> /etc/hosts
          apt-get update && apt-get install -y [ANY_EXTRA_DEPS]
      - name: Setup database
        run: |
          bundle exec rake db:create db:migrate RAILS_ENV=test
      - name: Run tests
        run: bundle exec rspec

Enter fullscreen mode Exit fullscreen mode

🟥 2. GitLab CI/CD

📄 File: .gitlab-ci.yml


image:
  name: [IMAGE]:[VERSION]
  entrypoint: [""]

services:
  - name: postgres
    alias: postgres
  - name: redis
    alias: redis

variables:
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: pipeline
  RAILS_ENV: test
  SESSION_EXP_MINUTES: 30
  UI_HOST_URL: http://react-app
  MAILER_SENDER: [email protected]

test:
  before_script:
    - echo "127.0.0.1 postgres" >> /etc/hosts
    - bundle install
  script:
    - bundle exec rake db:create db:migrate
    - bundle exec rspec

Enter fullscreen mode Exit fullscreen mode

Bonus: If .gitlab-ci.yml breaks and you spend 3 hours fixing indentation, congratulations — you’ve unlocked “YAML Warrior” 🥷.


☕ 3. Jenkins (Declarative)

📄 File: Jenkinsfile


pipeline {
  agent {
    docker {
      image '[IMAGE]:[VERSION]'
      args '-v /etc/hosts:/etc/hosts'
      registryUrl 'https://index.docker.io/v1/'
      registryCredentialsId 'DOCKER_HUB_CREDS'
    }
  }
  environment {
    POSTGRES_USER = 'postgres'
    POSTGRES_PASSWORD = 'pipeline'
    RAILS_ENV = 'test'
    SESSION_EXP_MINUTES = '30'
    UI_HOST_URL = 'http://react-app'
    MAILER_SENDER = '[email protected]'
  }
  stages {
    stage('Setup') {
      steps {
        sh 'echo "127.0.0.1 postgres" >> /etc/hosts'
        sh 'apt-get update && apt-get install -y [ANY_EXTRA_DEPS]'
      }
    }
    stage('Test') {
      steps {
        sh 'bundle exec rake db:create db:migrate'
        sh 'bundle exec rspec'
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Jenkins: because you like your CI with a retro web UI and just the right amount of pain 😅.


🔵 4. CircleCI

📄 File: .circleci/config.yml


version: 2.1

jobs:
  test:
    docker:
      - image: [IMAGE]:[VERSION]
        auth:
          username: $DOCKER_HUB_USERNAME
          password: $DOCKER_HUB_PASSWORD
      - image: postgres
        environment:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: pipeline
      - image: redis
    environment:
      RAILS_ENV: test
      SESSION_EXP_MINUTES: 30
      UI_HOST_URL: http://react-app
      MAILER_SENDER: [email protected]
    steps:
      - run:
          name: Configure hosts
          command: echo "127.0.0.1 postgres" >> /etc/hosts
      - run:
          name: Setup database
          command: bundle exec rake db:create db:migrate
      - run:
          name: Run tests
          command: bundle exec rspec

Enter fullscreen mode Exit fullscreen mode

CircleCI: for when you want your pipeline to look like a circle, but work like a rocket 🚀.


🟨 5. Bitbucket Pipelines

📄 File: bitbucket-pipelines.yml


image: [IMAGE]:[VERSION]

pipelines:
  default:
    - step:
        name: Run Tests
        services:
          - postgres
          - redis
        caches:
          - bundler
        script:
          - echo "127.0.0.1 postgres" | tee -a /etc/hosts
          - bundle install --path vendor/bundle
          - bundle exec rake db:create db:migrate
          - bundle exec rspec

definitions:
  services:
    postgres:
      image: postgres
      environment:
        POSTGRES_USER: postgres
        POSTGRES_PASSWORD: pipeline
    redis:
      image: redis

options:
  size: 2x # Double resources if needed

Enter fullscreen mode Exit fullscreen mode

🗒 Notes:

  • Docker image : use your custom image with Ruby, Node, etc.
  • Services : Postgres and Redis are easily attachable.
  • ENV vars : you can inject RAILS_ENV, SESSION_EXP_MINUTES, etc. via Repository Settings → Repository variables.
  • Hosts hack : Bitbucket containers sometimes need /etc/hosts updated just like the others.

🔁 Key Patterns Across All Platforms

  • ✅ Use custom Docker images with auth
  • 🔧 Spin up Redis and Postgres as services
  • 🧬 Standardize ENV variables
  • 🧠 Use /etc/hosts hack to make your services resolvable
  • 🧪 Setup database , then run tests

🚀 Bonus Tip: Speed Up Builds Like a Pro

💾 Dependency Caching (GitHub Actions Example)


- name: Cache Bundler
  uses: actions/cache@v3
  with:
    path: vendor/bundle
    key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}

- name: Cache Yarn
  uses: actions/cache@v3
  with:
    path: node_modules
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}

Enter fullscreen mode Exit fullscreen mode

“Caching saves time. And so does skipping meetings. But caching won’t get you fired.”

🧪 Parallel Testing (RSpec Example)


jobs:
  test:
    strategy:
      matrix:
        files: [spec/controllers/*_spec.rb, spec/models/*_spec.rb, ...]
    steps:
      - run: bundle exec rspec ${{ matrix.files }}

Enter fullscreen mode Exit fullscreen mode

Split tests, conquer time. Why run them one-by-one when your CI runners are just waiting to flex?


Article content

And sure, at the end of the process, you might just get an error message in bright red , with a giant ❌ glaring at you like it’s judging your life choices. But hey— it’s better to be warned by your CI than yelled at by a frustrated user when the system goes down. Errors caught early are lessons; errors in production are tickets. Lots of tickets.


🏁 Final Thoughts

CI doesn’t have to be confusing. By unifying your setup across platforms, you can maintain consistent behavior, reduce surprises, and impress that one developer on your team who always says “Did you lint this?”

So plug in your [IMAGE], add your [VERSION], and go forth — because real devs don’t push straight to main without tests. (Right?… Right??)

Article content

Top comments (0)