Rails 8 (released November 2024) is the smoothest upgrade yet. The focus? Simplicity and performance - removing external dependencies while boosting speed.
Solid Queue, Solid Cache, and Solid Cable eliminate Redis for many use cases. Built-in authentication removes the need for Devise. Ruby 3.4 (latest stable) with continued YJIT improvements delivers excellent performance.
Note: This is Part 5 of our Rails Upgrade Series. Read Part 1: Planning Rails Upgrade for the overall strategy.
Before We Start
Expected Timeline: 1-2 weeks for medium-sized applications (easiest upgrade!)
Medium-sized application: 20,000-50,000 lines of code, 30-100 models, moderate test coverage, 2-5 developers. Smaller apps may take 1-2 weeks, larger enterprise apps 6-12 weeks.
Prerequisites:
- Currently on Rails 7.2 (upgrade from 7.1 first if needed)
- Ruby 3.1.0+ installed (Ruby 3.4 strongly recommended)
- Test coverage of 80%+
- Understanding of background job setup
Step 1: Upgrade Ruby to 3.4 (Recommended)
Rails 8 requires Ruby 3.1.0 minimum, but Ruby 3.4 (latest stable) is strongly recommended for maximum performance.
Why Ruby 3.4?
Ruby 3.4 (December 2024) - Latest stable:
itas default block parameter - Cleaner block syntax- Prism parser improvements - Faster parsing
- YJIT optimizations - Continued performance improvements
- Better memory efficiency - Reduced memory usage
- Enhanced pattern matching - More powerful syntax
- Improved error messages - Better debugging experience
Ruby 3.3 (December 2023) - Also excellent:
- Prism parser - New default parser
- RJIT - Pure Ruby JIT compiler
- M:N thread scheduler - Better concurrency
- 15-20% faster than Ruby 3.2 with YJIT
Ruby 3.2 (December 2022) - Stable choice:
- Production-ready YJIT - Stable and fast
- WASI support - WebAssembly integration
- Data class - Immutable value objects
Upgrade Ruby
# Check current Ruby version
ruby -v
# Install Ruby 3.4 (recommended - latest stable)
rbenv install 3.4.1
rbenv local 3.4.1
# Or Ruby 3.3 (also good)
rbenv install 3.3.6
rbenv local 3.3.6
# Verify
ruby -v
# => ruby 3.4.1
# Update bundler
gem install bundler
bundle installEnable YJIT (Critical for Performance)
# config/boot.rb
ENV['RUBY_YJIT_ENABLE'] = '1'
# Or set in environment
export RUBY_YJIT_ENABLE=1Performance gain: Ruby 3.4 continues YJIT improvements, 15-20% faster than Ruby 3.2, or 30-40% faster than Ruby 2.7.
Test with Ruby 3.4
# Run full test suite
bundle exec rails test
# Check YJIT stats
rails runner 'puts RubyVM::YJIT.runtime_stats' | grep ratioStep 2: Update the Gemfile
# Gemfile
# Update Rails
gem 'rails', '~> 8.0.0'
# Solid Queue (replaces Sidekiq/Resque for many use cases)
gem 'solid_queue'
# Solid Cache (database-backed caching)
gem 'solid_cache'
# Solid Cable (WebSocket without Redis)
gem 'solid_cable'
# Keep existing gems
gem 'importmap-rails'
gem 'turbo-rails'
gem 'stimulus-rails'
gem 'sprockets-rails'
gem 'puma', '>= 5.0'
gem 'bootsnap', require: false
# Database adapters
gem 'pg', '~> 1.1' # PostgreSQL
# or
gem 'mysql2', '~> 0.5' # MySQL
# Optional: Remove if migrating to Solid alternatives
# gem 'sidekiq' # Can be replaced by Solid Queue
# gem 'redis' # Can be replaced by Solid Cache/Cablebundle update rails
bundle installStep 3: Run the Update Task
rails app:updateReview changes to:
config/application.rbconfig/environments/*.rbconfig/initializers/new_framework_defaults_8_0.rb
Step 4: Solid Queue (Optional but Recommended)
Solid Queue is a database-backed job queue that eliminates Redis dependency for background jobs.
When to Use Solid Queue
Good fit:
- Low to medium job volume (< 1000 jobs/minute)
- Simple job processing needs
- Want to eliminate Redis
- PostgreSQL or MySQL database
Stick with Sidekiq/Resque if:
- High job volume (> 1000 jobs/minute)
- Complex job scheduling needs
- Already have Redis infrastructure
- Need advanced features (unique jobs, batches)
Install Solid Queue
# Install Solid Queue
rails solid_queue:install
# This creates:
# - db/queue_schema.rb
# - config/queue.yml
# - bin/jobs (worker script)# Run migrations
rails db:migrateConfigure Solid Queue
# config/queue.yml
production:
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 3
processes: 2
polling_interval: 0.1# config/environments/production.rb
config.active_job.queue_adapter = :solid_queueMigrate from Sidekiq
# Before (Sidekiq)
class MyJob < ApplicationJob
queue_as :default
def perform(user_id)
# Job logic
end
end
# After (Solid Queue) - Same code!
class MyJob < ApplicationJob
queue_as :default
def perform(user_id)
# Job logic - no changes needed
end
endRun Solid Queue
# Development
bin/jobs
# Production (with systemd, Docker, or Kamal)
bundle exec rake solid_queue:startStep 5: Solid Cache (Optional)
Solid Cache is a database-backed cache store that eliminates Redis for caching.
Install Solid Cache
rails solid_cache:install
rails db:migrateConfigure Solid Cache
# config/environments/production.rb
config.cache_store = :solid_cache_storeUsage (Same as Before)
# Fragment caching - no changes
<% cache @post do %>
<%= render @post %>
<% end %>
# Low-level caching - no changes
Rails.cache.fetch("user_#{user.id}") do
user.expensive_calculation
endPerformance Considerations
Pros:
- No Redis dependency
- Automatic cleanup of old entries
- Works with existing database
Cons:
- Slower than Redis for high-traffic sites
- Database load increases
Recommendation: Use Solid Cache for low to medium traffic. Keep Redis for high-traffic applications.
Step 6: Solid Cable (Optional)
Solid Cable provides WebSocket support without Redis.
Install Solid Cable
rails solid_cable:install
rails db:migrateConfigure Solid Cable
# config/cable.yml
production:
adapter: solid_cable
# Or keep Redis if we have it
# production:
# adapter: redis
# url: redis://localhost:6379/1Usage (No Changes)
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room_id]}"
end
def receive(data)
ActionCable.server.broadcast(
"chat_#{params[:room_id]}",
data
)
end
endStep 7: Built-in Authentication Generator
Rails 8 includes a built-in authentication generator - no Devise needed for simple use cases.
Generate Authentication
rails generate authenticationThis creates:
Usermodel with password authenticationSessionsControllerfor login/logoutPasswordsControllerfor password reset- Authentication views
- Helper methods
What We Get
# app/models/user.rb
class User < ApplicationRecord
has_secure_password
generates_token_for :password_reset, expires_in: 15.minutes
generates_token_for :email_confirmation, expires_in: 24.hours
end# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate
private
def authenticate
if session_record = Session.find_by(id: cookies.signed[:session_id])
Current.session = session_record
else
redirect_to new_session_path
end
end
endWhen to Use Built-in Auth vs Devise
Use built-in authentication:
- Simple authentication needs
- Want full control over auth code
- Learning Rails authentication
- Small to medium applications
Use Devise:
- Need OAuth integration
- Complex authentication requirements
- Multi-tenancy
- Advanced features (confirmable, lockable, etc.)
Migrate from Devise (If Needed)
# Keep the existing User model
# Add has_secure_password
class User < ApplicationRecord
has_secure_password
# Keep existing Devise functionality we need
# Remove Devise modules we don't use
end
# Gradually migrate authentication logic
# Test thoroughly before removing DeviseStep 8: Progressive Web App (PWA) Support
Rails 8 adds built-in PWA support.
Generate PWA Files
rails generate pwaThis creates:
app/views/pwa/manifest.json.erb- PWA manifestapp/views/pwa/service-worker.js- Service worker- Icons and configuration
Configure PWA
<!-- app/views/layouts/application.html.erb -->
<head>
<%= tag.link rel: "manifest", href: pwa_manifest_path %>
<%= tag.meta name: "apple-mobile-web-app-capable", content: "yes" %>
</head># config/routes.rb
get "manifest" => "pwa#manifest", as: :pwa_manifest
get "service-worker" => "pwa#service_worker", as: :pwa_service_workerStep 9: Breaking Changes (Minimal!)
Rails 8 has very few breaking changes - this is the smoothest upgrade.
1. Deprecations from Rails 7 Removed
# If we fixed Rails 7 deprecation warnings, we're good!
# Check for any remaining warnings:
RAILS_ENV=test rails test 2>&1 | grep -i deprecat2. Default Configuration Changes
# config/initializers/new_framework_defaults_8_0.rb
# Review and enable new defaults
Rails.application.config.load_defaults 8.03. ActiveStorage Changes
# ActiveStorage::Blob#open without block now returns file
# Before (Rails 7)
blob.open do |file|
# Use file
end
# After (Rails 8) - both work
blob.open do |file|
# Use file
end
# Or without block
file = blob.open
# Use file
file.closeStep 10: Testing Updates
Test Solid Queue Jobs
# test/jobs/my_job_test.rb
require "test_helper"
class MyJobTest < ActiveJob::TestCase
test "performs job" do
assert_enqueued_with(job: MyJob, args: [1]) do
MyJob.perform_later(1)
end
end
test "processes job" do
MyJob.perform_now(1)
# Assert job effects
end
endTest Authentication
# test/controllers/posts_controller_test.rb
class PostsControllerTest < ActionDispatch::IntegrationTest
setup do
@user = users(:one)
sign_in @user
end
test "should get index" do
get posts_url
assert_response :success
end
endStep 11: Performance Improvements
Rails 8 + Ruby 3.4 delivers significant performance gains:
- 15-20% faster than Ruby 3.2 (or 30-40% faster than Ruby 2.7)
- Lower memory usage (Ruby 3.4 improvements)
- Faster boot times (Prism parser improvements)
- Reduced infrastructure costs (no Redis needed)
Benchmark YJIT Performance
# config/initializers/yjit_stats.rb
if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
Rails.application.config.after_initialize do
at_exit do
stats = RubyVM::YJIT.runtime_stats
puts "\n=== YJIT Stats ==="
puts "Compiled: #{stats[:compiled_iseq_count]} methods"
puts "Ratio: #{stats[:ratio]}%"
puts "==================\n"
end
end
endMonitor Solid Queue Performance
# Check job queue depth
SolidQueue::Job.pending.count
# Check failed jobs
SolidQueue::Job.failed.count
# Monitor in production
# Use the APM tool (New Relic, Datadog, etc.)Step 12: Deployment with Kamal 2
Rails 8 includes Kamal 2 for zero-downtime deployments.
Install Kamal
# Kamal is included by default
# Initialize configuration
kamal initConfigure Kamal
# config/deploy.yml
service: myapp
image: myapp/production
servers:
web:
hosts:
- 192.168.1.1
labels:
traefik.http.routers.myapp.rule: Host(`myapp.com`)
options:
network: "private"
registry:
server: ghcr.io
username: myusername
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEYDeploy
# First deployment
kamal setup
# Subsequent deployments
kamal deploy
# Rollback if needed
kamal rollbackUpgrade Checklist
Note: This checklist covers the most common changes. Depending on the application’s gems, custom code, and architecture, we may encounter additional issues. Always test thoroughly in a staging environment.
- Upgrade Ruby to 3.1+ (3.4 recommended)
- Enable YJIT for performance
- Update Gemfile with Rails 8.0
- Run
rails app:update - Decide on Solid Queue (optional)
- Decide on Solid Cache (optional)
- Decide on Solid Cable (optional)
- Consider built-in authentication (optional)
- Enable PWA support (optional)
- Fix any deprecation warnings
- Run full test suite
- Test in staging environment
- Deploy to production with monitoring
Common Gotchas
1. Solid Queue vs Sidekiq
# Solid Queue doesn't support all Sidekiq features
# Check compatibility before migrating:
# - Unique jobs -> Use database constraints
# - Batches -> Implement manually
# - Scheduled jobs -> Supported
# - Retries -> Supported2. Database Load with Solid Cache
# Monitor database performance
# Solid Cache adds queries to our database
# Consider keeping Redis for high-traffic sites
# Check cache hit rate
Rails.cache.stats3. Authentication Migration
# Don't rush to remove Devise
# Test built-in auth thoroughly first
# Migrate gradually if neededMigration Strategy
Conservative Approach
- Upgrade to Rails 8 first
- Keep existing infrastructure (Redis, Sidekiq, Devise)
- Test thoroughly
- Gradually adopt Solid gems if beneficial
Progressive Approach
- Upgrade Ruby to 3.4
- Upgrade to Rails 8
- Migrate to Solid Queue for new jobs
- Evaluate Solid Cache for non-critical caching
- Keep Redis for high-traffic features
Aggressive Approach
- Upgrade Ruby to 3.4
- Upgrade to Rails 8
- Migrate all jobs to Solid Queue
- Replace Redis with Solid Cache/Cable
- Use built-in authentication for new features
Recommendation: Start conservative, move progressive as we gain confidence.
What’s Next
Congratulations!
We’ve completed the Rails upgrade journey from planning through Rails 8.
Series recap:
- Part 1: Strategic planning and preparation
- Part 2: Rails 4.2 to 5 - Foundation updates
- Part 3: Rails 5.2 to 6 - Zeitwerk and Webpacker
- Part 4: Rails 6.1 to 7 - Import Maps and Hotwire
- Part 5: Rails 7.2 to 8 - Solid gems and simplification
Keep the Rails App Modern
- Monitor deprecation warnings in each Rails version
- Upgrade Ruby regularly for performance and security
- Test thoroughly at each step
- Stay informed about Rails releases
- Contribute back to the Rails community
Resources
- Official Rails 8.0 Release Notes
- Rails Upgrade Guide
- Solid Queue Documentation
- Solid Cache Documentation
- Kamal Documentation
- Ruby 3.4 Release Notes
- Ruby 3.3 Release Notes
- RailsDiff 7.2 to 8.0
At Saeloun, we’ve helped numerous teams successfully upgrade to Rails 8 and modernize their infrastructure.
Whether planning a major upgrade or needing help optimizing a Rails 8 application, we’re here to help.
Contact us for Rails upgrade consulting
