Rails 8 is the easiest major upgrade in years. Most of what’s new is opt-in, the deprecation list is short, and if your app is already on Rails 7.2 with a green test suite, the version bump itself can land in an afternoon. The work is in knowing which of the new defaults to adopt, which to skip, and what to check before you deploy.
We maintain Rails applications in production for a living, including an 8-figure e-commerce platform where our founder is the CTO, and the site you’re reading this on runs Rails 8.1. This is the process we use.
Before you start
Three prerequisites, in order:
Be on Rails 7.2 first. The official upgrade guide is explicit about this: move to 7.2, make sure your app runs as expected, then take on 8.0. If you’re on 7.0 or 7.1, do those hops first. Each one is small; skipping them means debugging three versions of behavior changes at once.
Ruby 3.2 or newer. Rails 8.0 and 8.1 require Ruby 3.2.0+. If you’re behind, upgrade Ruby as its own deploy before touching the Rails version. A Ruby upgrade and a Rails upgrade in the same release is a bad time when something breaks and you can’t tell which one did it.
A test suite you trust. The upgrade guide’s first step is “write tests and make sure they pass” for a reason. We target 80-90% coverage on the apps we maintain, and every upgrade we’ve shipped without downtime leaned on that. If your coverage is thin, spend a week adding request specs around the money paths (checkout, signup, billing) before you begin.
Dual boot the upgrade
For anything beyond a trivial app, we dual boot: the app runs against both Rails versions from the same branch, so you can fix incompatibilities incrementally while normal feature work continues.
# Gemfile
rails_version = ENV.fetch("RAILS_VERSION", "7.2")
gem "rails", "~> #{rails_version}.0"
# Generate a lockfile for the next version alongside your current one
RAILS_VERSION=8.0 BUNDLE_GEMFILE=Gemfile.next bundle install
# Run the suite against Rails 8 whenever you want
RAILS_VERSION=8.0 BUNDLE_GEMFILE=Gemfile.next bin/rails test
(The next_rails gem packages this pattern nicely if you’d rather not wire it by hand.)
Run the suite against 8.0, fix what fails, and keep shipping from the 7.2 boot until the 8.0 run is green.
The gem sweep
Before the version bump, audit your Gemfile. For each gem, check whether the released version
supports Rails 8. bundle outdated gives you the list; the gem’s changelog or GitHub issues tell
you the rest. In our experience the usual suspects are admin frameworks, pagination, soft-delete
gems, and anything that monkeypatches Active Record internals.
This is also the moment to delete gems you no longer need. Rails 8 ships with more built in (authentication scaffolding, queue, cache), and every dependency you remove is one less thing to sweep on the next upgrade.
The bump itself
With the suite green under dual boot:
bundle update rails
bin/rails app:update
app:update walks you through conflicts between your config files and the new defaults, and
generates config/initializers/new_framework_defaults_8_0.rb with every new default commented
out. Do not flip them all at once. Enable them one at a time, run the suite, deploy, and only
after they’re all enabled change the master switch:
# config/application.rb
config.load_defaults 8.0
Then delete the initializer. Keeping load_defaults at your old version while you work through
the file is the supported, boring, correct path.
What actually changes in Rails 8
The headline features are defaults for new apps. Existing apps keep working as configured, which is why this upgrade is gentle. Still, several are worth adopting deliberately:
Solid Queue, Solid Cache, Solid Cable. New Rails 8 apps run background jobs, caching, and Action Cable through the database instead of Redis. Your existing Sidekiq/Redis setup keeps working; nothing forces a migration. The honest evaluation question is whether your job volume justifies Redis as a dependency at all. For many mid-sized apps it doesn’t, and moving to Solid Queue deletes an entire piece of infrastructure from the ops bill. Note that the Solid libraries expect their own databases (or at least their own schemas), so factor that into your hosting setup before opting in. On this site, a low-traffic marketing app with one Postgres database, we looked at Solid Queue and deliberately kept the in-process async adapter instead; the right amount of queue infrastructure for a contact form is none. Decide per app, after the upgrade lands, not during it.
Propshaft. The new default asset pipeline for new apps. If you’re on Sprockets, it still works and there’s no forced migration. Propshaft is simpler (no asset transpilation, just digest-stamping and serving), so the move mostly matters if your Sprockets setup is already trivial.
Kamal 2 and Thruster. New apps ship with deployment via Kamal and the Thruster HTTP proxy in front of Puma. If you’re on Heroku or Render, this changes nothing. It’s an option, and a good one, if you want to move to your own servers.
The authentication generator. bin/rails generate authentication creates a session-based
auth setup with password reset, using code you own instead of an engine. Not something an upgrade
requires, but worth knowing before you reach for a gem on your next project.
Breaking changes to check
The list is short, and that’s the point. The ones that have actually bitten in the wild:
Enum keyword arguments. The old enum status: { ... } keyword form was removed after
deprecation in 7.2. Use the positional form:
# Before (removed in 8.0)
enum status: { draft: 0, published: 1 }
# After
enum :status, { draft: 0, published: 1 }
db:migrate on a fresh database now loads the schema first, then runs pending migrations,
instead of replaying every migration from the beginning of time. This is almost always what you
wanted anyway, but if your CI or a setup script depended on the full replay, it will behave
differently.
Removed deprecated configuration. config.read_encrypted_secrets,
config.active_record.commit_transaction_on_non_local_return,
config.active_record.warn_on_records_fetched_greater_than, and friends are gone. If your config
still sets any of these, 7.2’s deprecation warnings will have told you; this is why running
deprecation-free on 7.2 first matters.
And Rails 8.1
Once you’re stable on 8.0, the hop to 8.1 is minor. The most visible change is that schema.rb
now sorts table columns alphabetically, so expect a one-time large diff in your schema file the
first time you migrate. It’s cosmetic, and it ends the schema-churn problem where two developers’
machines produced different column orders.
The deploy
We ship the upgrade as its own deploy with nothing else in it, watch error rates and queue latency for a day, and only then start adopting the new defaults and features. If something goes wrong, rolling back a pure version-bump deploy is clean; rolling back a version bump tangled with feature work is not.
If your app is still back on Rails 6 or early 7 and this all sounds like a lot, that’s the situation we get hired into most often. The path is the same, just longer, and it’s cheaper to walk it now than after the next security advisory forces the issue.