JT Book a call

Blog

Rails 8.0 Migration: A Phased Upgrade Plan for Large Apps

Author

JetThoughts Team

Published

10 min read

In this post

Rails 8.0 changes several defaults at once: a new asset pipeline, database-backed cache, queue and Action Cable adapters, an authentication generator, and Kamal 2 deployment. For a large application you do not have to adopt all of it. The upgrade and the adoption of each new default are separate decisions.

This guide splits the work into phases, and every Rails 8 claim in it is checked against the Rails 8.0 release notes, the Rails upgrade guide or the README of the relevant gem. It gives no cost or timeline numbers, because none of them can be sourced: how long your upgrade takes depends on your test coverage, gem list and traffic.

Need engineers for this? See how we work.

What Rails 8.0 actually changes #

The release notes list these major features:

  • Kamal 2. Rails comes preconfigured with Kamal 2 for deployment. Kamal 2 ships its own proxy, Kamal Proxy, which replaces the Traefik option it used at launch.
  • Thruster. The generated Dockerfile includes Thruster, a proxy in front of Puma that provides X-Sendfile acceleration, asset caching and asset compression.
  • Solid Cable. Replaces Redis as the pubsub server that relays WebSocket messages between processes. Messages are kept in the database for a day by default.
  • Solid Cache. Replaces Redis or Memcached for storing HTML fragment caches in particular.
  • Solid Queue. Replaces the need for Redis and for a separate job framework such as Resque, Delayed Job or Sidekiq. It uses FOR UPDATE SKIP LOCKED, available in PostgreSQL 9.5+ and MySQL 8.0+, and also works with SQLite.
  • Propshaft. The default asset pipeline, replacing Sprockets.
  • Authentication generator. bin/rails generate authentication creates a starting point for a session-based, password-resettable, metadata-tracking authentication system.

Two things the release notes do not say, so this guide does not either: that these defaults are cheaper to run, or that they are faster than Redis. Solid Cache’s README argues that a large disk-backed cache can beat a small memory-only one, but you should measure that on your own traffic.

Rails 8.0 requires Ruby 3.2.0 or newer, according to the upgrade guide.

What the authentication generator creates #

Per the generator source in Rails 8.0.0, it generates User, Session and Current models, SessionsController and PasswordsController, an Authentication controller concern, a passwords mailer with reset views, routes, and migrations for users (email address and password_digest) and sessions (IP address and user agent). It enables bcrypt.

It is deliberately small. It does not generate email confirmation, account lockout, multi-factor authentication, password complexity rules or session timeouts. If your compliance requirements include any of those, you keep your current library (Devise or similar) or build them on top. Do not copy a config.authentication.* block from any article: no such configuration API exists in Rails.

Phase 0: Assess before you touch the Gemfile #

rails -v
bundle outdated --parseable

Then list what Rails 8 could let you drop, and decide per item whether you want to:

  • Redis for caching, sessions, jobs or Action Cable
  • Memcached for fragment caching
  • Sidekiq, Resque or Delayed Job
  • Sprockets configuration
  • Devise or other authentication gems (keep them if you need features the generator does not provide)

The upgrade guide’s advice applies: have good test coverage before you start, upgrade Ruby and Rails separately, upgrade to the latest Ruby you can first, and move one minor version at a time to make use of deprecation warnings.

Phase 1: Reach Rails 7.2 cleanly #

Rails 8.0 and 8.1 require Ruby 3.2.0+, while Rails 7.2 requires Ruby 3.1.0+. So the sequence is:

  1. Upgrade to the newest Ruby you can on your current Rails version.
  2. Move to Rails 7.2 one minor version at a time.
  3. Run bin/rails app:update after changing the Rails version in the Gemfile and walk through the interactive diff.
  4. Fix every deprecation warning. A quick way to make them fail loudly in test is to set the behavior in config/environments/test.rb:
# config/environments/test.rb
config.active_support.deprecation = :raise
  1. Run the full test suite before continuing.

Phase 2: Move to Rails 8.0 without adopting new defaults #

# Gemfile
gem "rails", "~> 8.0.0"
bundle update rails
bin/rails app:update

The update task creates config/initializers/new_framework_defaults_8_0.rb. Per the upgrade guide, your app keeps the previous version’s defaults until you change config.load_defaults, and you enable the new defaults one by one by uncommenting them in that file. Do that gradually, then set config.load_defaults 8.0 once everything is on.

At this point the app is on Rails 8.0 but still uses Redis, Sidekiq and Sprockets if it did before. That is a valid place to stop and ship. The 8.0 release notes describe the Solid gems as replacements, not requirements.

Phase 3: Adopt the new defaults one at a time #

Each item below is independent. Ship and watch one before starting the next.

Solid Cache #

Per the Solid Cache README: add the gem, run bin/rails solid_cache:install, then add the cache database to config/database.yml (with migrations_paths: db/cache_migrate) and run db:prepare in production. The installer sets the production cache store, which you can also set by hand:

# config/environments/production.rb
config.cache_store = :solid_cache_store

If you want a separate cache database, config.solid_cache.connects_to (or connects_to in config/solid_cache.yml) points the cache at it. The README also documents encrypting cache values through Active Record Encryption, which matters if you cache personal data.

Rollout advice: the cache API (Rails.cache.read, write, fetch) is the same across stores, so the switch is a configuration change. Expect a cold cache on cutover. Compare p50 and p95 request time and cache hit rate before and after, on your own traffic.

Solid Queue #

Per the Solid Queue README: add the gem, run bin/rails solid_queue:install. That configures Solid Queue as the production Active Job backend, creates config/queue.yml and config/recurring.yml and db/queue_schema.rb, and creates a bin/jobs executable. It also adds this to production:

config.active_job.queue_adapter = :solid_queue
config.solid_queue.connects_to = { database: { writing: :queue } }

Jobs only run if Solid Queue is running: start bin/jobs, or run it inside Puma with plugin :solid_queue in puma.rb.

A sample config/queue.yml (the README’s defaults are polling_interval of 1 second for dispatchers, 0.1 seconds for workers, and a dispatcher batch_size of 500):

production:
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 3
      processes: 1

Rollout advice: the README supports adopting it one job at a time. Leave config.active_job.queue_adapter on your old backend and set self.queue_adapter = :solid_queue inside the jobs you are moving. Job classes that only use the Active Job API need no other change, but anything that calls Sidekiq-specific APIs (sidekiq_options, Sidekiq middleware, Sidekiq Pro or Enterprise features) must be rewritten or kept on Sidekiq. Drain the old queue before removing the old gem.

Solid Cable #

Per the Solid Cable README: bin/rails solid_cable:install overwrites config/cable.yml to use the solid_cable adapter and creates db/cable_schema.rb. The database name in config/cable.yml must match the one in config/database.yml. The adapter configuration lives in config/cable.yml, not in production.rb. The README notes Solid Cable polls the database for updates, and that it is a good alternative to the PostgreSQL adapter, which has an 8 kB payload limit.

Propshaft #

Propshaft is the Rails 8 default for new apps, but an existing app moves only if you change it. There is no config.assets.pipeline = :propshaft setting; you swap gems in the Gemfile. The Propshaft upgrade guide states that Propshaft has a smaller scope than Sprockets, so migrating also means adopting jsbundling-rails and cssbundling-rails (or another bundler), and that it depends on Rails 7+. If your app uses Sass compilation, Webpacker or Sprockets-specific directives, budget real time here, or stay on Sprockets.

Authentication generator #

Only consider it for apps without an existing authentication system, or when you are deliberately replacing a simple one. Migrating existing users from Devise is a data migration: Devise stores a bcrypt hash in encrypted_password, and the generator expects password_digest. Both are bcrypt hashes, so the migration copies the hash across. Do not re-hash the existing hash with BCrypt::Password.create, which would break every login.

# db/migrate/XXXX_copy_devise_password_hashes.rb
class CopyDevisePasswordHashes < ActiveRecord::Migration[8.0]
  def up
    add_column :users, :password_digest, :string
    execute "UPDATE users SET password_digest = encrypted_password"
  end

  def down
    remove_column :users, :password_digest
  end
end

Keep encrypted_password in place until the new login flow has run in production, then drop it in a later migration. Test this against a copy of production data first. This sketch assumes a plain Devise database_authenticatable setup with the default bcrypt hashing, so check that your app does not use a custom pepper or stretches setting.

Kamal 2 #

The Rails 8.0.0 generated config/deploy.yml uses a proxy: section (Kamal Proxy) rather than Traefik labels:

service: myapp
image: your-user/myapp

servers:
  web:
    - 192.168.0.1

proxy:
  ssl: true
  host: app.example.com

registry:
  username: your-user
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  secret:
    - RAILS_MASTER_KEY

The template’s own comment says to remove the ssl section when you use multiple web servers and to terminate SSL at your load balancer instead. If you already deploy through Kubernetes, ECS or Capistrano, you do not have to switch to Kamal to use the rest of Rails 8.

Risk controls for large apps #

  • Rollback. Keep the old Redis, Sidekiq and Sprockets code paths until each new default has run cleanly in production. Make sure migrations are reversible and that the previous release can be redeployed.
  • Separate databases. The Solid gems read their tables through their own connects_to configuration, so cache, queue and cable data can live in separate databases (the READMEs show this with database.yml entries per service, on SQLite, PostgreSQL or MySQL). Putting them on a separate database keeps cache and queue churn off your primary.
  • Test the jobs. Rails 7.2 changed tests to respect config.active_job.queue_adapter (upgrade guide). If your tests assumed the test adapter while a different adapter was configured, they can fail during the 7.2 step.
  • Measure, don’t assume. Record request time percentiles, cache hit rate, job latency and database load before each change, and compare after.

FAQ #

When should I migrate to Rails 8? Once you are on Rails 7.2, run Ruby 3.2 or newer, have no outstanding deprecation warnings, and your key gems support Rails 8. Check each gem’s changelog or issue tracker; there is no shortcut script for this.

Can I keep using Redis with Rails 8? Yes. The release notes describe the Solid gems as replacements you can adopt; Rails still ships the Redis cache store and Action Cable Redis adapter, and Solid Queue’s README documents adopting it one job at a time next to an existing backend.

Will it cut our infrastructure cost? Possibly, if it lets you retire Redis or Memcached servers. But it moves load onto your database, so the net effect depends on your setup. Measure it; do not budget on a percentage from a blog post.

How long will it take? It depends on your test coverage, gem list, Ruby version gap and how many of the new defaults you adopt. Treat the phases above as separate releases and estimate each from your own code. The fastest way to get a real estimate is to run bin/rails app:update on a branch and see how many deprecations and gem conflicts appear.

Is SQLite suitable for production? The release notes say Solid Queue works with SQLite, and the Solid Cache README uses SQLite files in its sample database.yml. Whether SQLite fits your workload (write concurrency, multiple app servers, backups) is a separate decision. For a multi-server application, keep PostgreSQL or MySQL.

Sources #

Want a second pair of hands on the upgrade? Our Rails sprint team can take a phase of it.

Read next

Tell us what you need built

JetThoughts has been building software since 2008. Book a call, or write to sales@jetthoughts.com. You'll leave the call with a written one-page scope within 2 working days.

#01 Paul Keen
Paul KeenFounder & Open Source Contributor

A 30-minute call; pick a time.