Rails developers often gravitate toward hosted CI services like CircleCI or GitHub Actions, but Jenkins remains a genuinely strong option, especially if your team already runs Jenkins for other services and wants Rails builds living in the same ecosystem. The main friction with Rails and Jenkins isn’t Jenkins itself — it’s making sure the agent has the right Ruby version, bundler setup, and any service dependencies (PostgreSQL, Redis) your test suite needs.
This guide covers the full setup, from installing Ruby on your agent to a complete pipeline that runs RSpec, RuboCop, and deploys your Rails app.
Why Jenkins for Rails?
Rails apps typically need more than “just run the tests” — you’ve got database migrations, asset compilation, gem dependencies that sometimes need native extensions compiled, and often background job processing to account for in your test environment. Jenkins pipelines handle all of this cleanly once configured, and because Jenkins pipelines are just code (Groovy in a Jenkinsfile), you get the same version-controlled, reviewable CI configuration that Rails developers already expect from their app code.
Jenkins Architecture Notes for Rails
Rails test suites almost always need a real database (PostgreSQL or MySQL) and often Redis for caching or Sidekiq job tests. The cleanest way to satisfy this in Jenkins is either running these as sidecar Docker containers during the pipeline, or using docker-compose to spin up the full test environment for the duration of the build, then tearing it down.
Prerequisites
- Jenkins server running
- Ruby installed on the agent (via rbenv, rvm, or system package) matching your app’s
.ruby-version - Bundler installed
- A Rails app in Git with a
Gemfileand test suite (RSpec or Minitest) - Docker installed on the agent if using containerized dependencies
Step 1: Install Ruby on the Jenkins Agent
Using rbenv (recommended for managing multiple Ruby versions):
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 3.3.0
rbenv global 3.3.0
Verify:
ruby -v
gem install bundler
Step 2: Install Required Jenkins Plugins
In Manage Jenkins > Plugins, install:
- Git Plugin
- Pipeline
- JUnit Plugin — RSpec can output JUnit-format XML that Jenkins can parse
- HTML Publisher Plugin — useful for viewing SimpleCov coverage reports
Step 3: A Basic Jenkinsfile for a Rails App
pipeline {
agent any
environment {
RAILS_ENV = 'test'
DATABASE_URL = 'postgres://postgres:postgres@localhost:5432/myapp_test'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/yourorg/rails-app.git'
}
}
stage('Install Dependencies') {
steps {
sh '''
eval "$(rbenv init -)"
bundle install --deployment --jobs=4
'''
}
}
stage('Setup Database') {
steps {
sh '''
eval "$(rbenv init -)"
bundle exec rails db:create
bundle exec rails db:schema:load
'''
}
}
stage('RuboCop') {
steps {
sh '''
eval "$(rbenv init -)"
bundle exec rubocop --format progress
'''
}
}
stage('RSpec Tests') {
steps {
sh '''
eval "$(rbenv init -)"
bundle exec rspec --format RspecJunitFormatter --out rspec.xml
'''
}
post {
always {
junit 'rspec.xml'
}
}
}
stage('Precompile Assets') {
steps {
sh '''
eval "$(rbenv init -)"
RAILS_ENV=production bundle exec rails assets:precompile
'''
}
}
}
}
Note: for RSpec to output JUnit XML, add the rspec_junit_formatter gem to your Gemfile’s test group.
Step 4: Using Docker Compose for Test Dependencies
A cleaner approach that avoids polluting the Jenkins agent with a system-wide PostgreSQL install is to spin up dependencies via Docker Compose just for the test run. Here’s a docker-compose.test.yml:
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp_test
ports:
- "5432:5432"
redis:
image: redis:7
ports:
- "6379:6379"
And the corresponding pipeline stages:
stage('Start Test Services') {
steps {
sh 'docker compose -f docker-compose.test.yml up -d'
}
}
stage('Run Tests') {
steps {
sh '''
eval "$(rbenv init -)"
bundle exec rspec --format RspecJunitFormatter --out rspec.xml
'''
}
post {
always {
junit 'rspec.xml'
sh 'docker compose -f docker-compose.test.yml down'
}
}
}
Step 5: Full Pipeline with Docker-Based Ruby Agent
An even more reproducible option is running the entire build inside an official Ruby Docker image, so the agent host doesn’t need Ruby installed at all:
pipeline {
agent {
docker {
image 'ruby:3.3'
args '--network=host'
}
}
environment {
RAILS_ENV = 'test'
DATABASE_URL = 'postgres://postgres:postgres@localhost:5432/myapp_test'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/yourorg/rails-app.git'
}
}
stage('Install Dependencies') {
steps {
sh 'bundle install --jobs=4'
}
}
stage('Setup DB and Test') {
steps {
sh '''
bundle exec rails db:create db:schema:load
bundle exec rspec --format RspecJunitFormatter --out rspec.xml
'''
}
post {
always {
junit 'rspec.xml'
}
}
}
}
}
Step 6: Deployment Stage
Most Rails apps deploy via Capistrano, Docker, or a platform like Heroku/Render. Here’s a Docker-based deployment stage as an example:
stage('Build Docker Image') {
steps {
sh "docker build -t myorg/rails-app:${BUILD_NUMBER} ."
}
}
stage('Push to Registry') {
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub-creds', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh "echo \$DOCKER_PASS | docker login -u \$DOCKER_USER --password-stdin"
sh "docker push myorg/rails-app:${BUILD_NUMBER}"
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh "kubectl set image deployment/rails-app rails-app=myorg/rails-app:${BUILD_NUMBER} --namespace=production"
}
}
If you’re deploying via Capistrano instead:
stage('Deploy with Capistrano') {
steps {
sh '''
eval "$(rbenv init -)"
bundle exec cap production deploy
'''
}
}
Handling Rails Credentials and Master Key
Rails encrypted credentials require the RAILS_MASTER_KEY to decrypt at boot. Store it as a Jenkins secret text credential and inject it during build/deploy stages:
stage('Precompile Assets') {
steps {
withCredentials([string(credentialsId: 'rails-master-key', variable: 'RAILS_MASTER_KEY')]) {
sh 'RAILS_ENV=production bundle exec rails assets:precompile'
}
}
}
System Test / Capybara Considerations
If your suite includes system tests using Capybara with a headless browser, install Chrome/Chromedriver on the agent or, more simply, use a Docker image that already bundles them, such as selenium/standalone-chrome as a sidecar container, or a Ruby image with headless Chrome preinstalled.
Troubleshooting
Bundler can’t find native gem dependencies (e.g., pg gem fails to build): Install the required system libraries first — for PostgreSQL’s gem, that’s libpq-dev on Debian/Ubuntu agents.
Tests fail with “database does not exist”: Confirm the database setup stage runs before tests, and DATABASE_URL or config/database.yml correctly points at the test database.
Asset precompilation fails with missing SECRET_KEY_BASE: Set a dummy SECRET_KEY_BASE environment variable for the precompile step, since Rails requires it even for asset compilation, or use RAILS_MASTER_KEY if credentials are encrypted.
Slow builds due to gem installation every time: Use bundle install --deployment with a persistent vendor/bundle cache directory shared across builds, or use a Docker layer cache if building inside containers.
Security Best Practices
- Never commit
config/master.key— injectRAILS_MASTER_KEYvia Jenkins Credentials - Run Brakeman (Rails-specific static security scanner) as a pipeline stage:
bundle exec brakeman -q - Use
bundle auditto check for gems with known CVEs - Restrict database credentials used in CI to a throwaway test database with no production access
FAQs
Can Jenkins handle multiple Ruby versions for different projects? Yes, using rbenv or rvm with per-project .ruby-version files, or by using different Docker images per pipeline that pin the exact Ruby version needed.
Do I need a real PostgreSQL database for Rails tests, or can I use SQLite? You can use SQLite for speed if your app doesn’t rely on Postgres-specific features, but it’s best practice to test against the same database engine you run in production to catch database-specific bugs early.
How do I run RuboCop as a quality gate that fails the build? bundle exec rubocop exits non-zero on offenses by default, which naturally fails the Jenkins stage — no special configuration needed beyond running it as a stage.
Can Jenkins deploy directly to Heroku? Yes, using the Heroku CLI within a pipeline stage (heroku container:push or git push heroku main), authenticated via an API key stored as a Jenkins credential.
What’s the best way to speed up Rails CI builds overall? Cache the bundler gem directory, parallelize your RSpec suite using parallel_tests, and consider Docker layer caching if you’re building inside containers.
Can Jenkins run Rails system tests that require a real browser? Yes, either by installing headless Chrome and Chromedriver directly on the agent, or by running a Selenium standalone Chrome container as a sidecar during the test stage — the latter is generally easier to keep updated and isolated from the rest of the build environment.
How do I keep secrets like API keys out of my Rails test environment configuration? Use Jenkins Credentials to inject them as environment variables at pipeline runtime rather than hardcoding them in config/environments/test.rb or committing them to .env files, and reference them in your Rails initializers via ENV.fetch.
Summary
Setting up Jenkins for Rails comes down to getting Ruby, bundler, and your test database dependencies properly available to the build agent — whether via rbenv on a persistent agent or a Docker-based ephemeral one — and then wiring together dependency installation, RuboCop, RSpec, asset precompilation, and deployment into a clean pipeline. Once that foundation is in place, Rails teams get the same reliable, repeatable CI/CD experience that Jenkins provides for any other language ecosystem.