
Rails 8.1 Local CI as First-Class Support
DHH unveiled Rails 8.1 during his keynote at Rails World 2025, releasing the first beta live on stage. This new version brings a suite of tools aimed at making Rails apps simpler to build, maintain, and collaborate on. With a strong focus on developer experience and consistent workflows, Rails 8.1 helps teams sidestep common frustrations and work together more smoothly.
Continuous Integration (CI) is a cornerstone of modern Rails development. It’s a common pain for developers to run tests with slightly different commands on their own machines, while CI servers like GitHub Actions or CircleCI use their own scripts; they might also have different versions of bundler or a specific gem installed causing differences in test results. Over time, these differences can cause unexpected failures in the pipeline, even when everything seemed to work fine locally.
Rails 8.1 introduces a solution: Local CI as a first-class feature.
With a simple DSL in config/ci.rb
and a new runner bin/ci
, it makes it easy to standardize workflows across local machines and CI servers.
Reference: Rails 8.1 Beta 1 Release Notes
Running tests locally with this setup can also help save money, since CI services often charge based on usage. If your CI pipeline takes a long time to run, running tests locally can be much faster, sometimes also saving developer time. In addition, CI services that rely on Docker images or other external services can sometimes fail due to issues unrelated to your code, such as service outages or network problems.
Defining CI Workflows with config/ci.rb
A small but powerful DSL for describing CI steps inside your app:
# config/ci.rb
ci.workflow do
task "Lint" do
run "bundle exec rubocop"
end
task "Tests" do
run "bin/rails test"
end
task "System Tests" do
run "bin/rails test:system"
end
end
Each task
gives a name to a step, while run
sets the command that will be executed. This workflow acts as the one reliable reference for how your app should be checked and validated.
Running Workflows Locally with bin/ci
Once you’ve declared tasks, you can run them locally with:
bin/ci
Rails will execute tasks in the order defined in config/ci.rb
. No more remembering multiple commands or wondering if you’re running the same checks as CI. That’s what config/ci.rb
looks like when generating a new app with Rails 8.1.0.beta1:
# config/ci.rb
CI.run do
step "Setup", "bin/setup --skip-server"
step "Style: Ruby", "bin/rubocop"
step "Security: Gem audit", "bin/bundler-audit"
step "Security: Importmap vulnerability audit", "bin/importmap audit"
step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
step "Tests: Rails", "bin/rails test"
step "Tests: System", "bin/rails test:system"
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
# Optional: set a green GitHub commit status to unblock PR merge.
# Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`.
# if success?
# step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
# else
# failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
# end
end
This last part shows how you can use the GitHub CLI (gh
) and the gh-signoff
extension to automatically set a green commit status when all CI steps pass. If uncommented, this code will mark your pull request as ready to merge when everything succeeds, or block merging if any step fails. This can help automate your workflow and keep your team’s process smooth.
Using Local CI in GitHub Actions (or Any CI Server)
The same workflow can be executed in CI pipelines by simply calling bin/ci
. For example, in GitHub Actions:
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.4
- run: bin/ci
This ensures CI servers and developers share exactly the same definition of “what counts as a passing build.”
Adding More Tasks
The DSL isn’t limited to tests. You can include security checks, JS linters, or database migrations:
task "Security Checks" do
run "bundle exec brakeman --no-pager"
run "bundle exec bundler-audit check --update"
end
task "JS Lint" do
run "yarn eslint app/javascript"
end
Anything you run in your pipeline today can live in config/ci.rb
.
To stay informed about new features and improvements to Local CI, consider following updates to this file in the Rails source code as the framework evolves.
Why This Matters
- Consistency: Developers and CI servers run the same workflow.
- Onboarding: New team members only need to know
bin/ci
. - Fewer Surprises: No more “green locally, red in CI” moments.
- Rails Philosophy: Batteries included. Rails now treats CI as part of the app itself.
Conclusion
It’s interesting to acknowledge that there might be possible limitations to it, since Rails 8.1’s Local CI is still new. For example, it isn’t designed to fully replace CI services like GitHub Actions or CircleCI, and it doesn’t handle things like matrix builds, caching, or secrets out of the box. Instead, it acts as a workflow definition layer—the “what” you want to run—while leaving the “how” and “where” to your existing CI systems. As the feature evolves, some of these limitations may be addressed or improved in future releases.
Rails 8.1’s Local CI feature is a small addition with big implications. By making config/ci.rb
the source of truth for workflows and shipping bin/ci
as the runner, Rails eliminates drift between local development and continuous integration.
Are you ready for Rails 8.1? Contact us and make sure you’re prepared for a smooth upgrade!