Ruby on Rails 8: The Complete Upgrade Guide with Solid Queue, Kamal, and Modern Deployment
Meta Description: Comprehensive guide to Rails 8 new features including Solid Queue for background jobs, Kamal deployment, Solid Cache, security improvements, and a complete upgrade roadmap with production examples.
The Hook: Why Rails 8 Changes the Operations Game
We've watched countless teams delay Rails upgrades for months, even years, fearing the inevitable breakage and migration headaches. The irony? Rails 8 is one of the most operationally significant releases in recent history—and delaying means missing out on features that directly reduce infrastructure complexity and deployment friction.
Rails 8 represents a strategic shift in how Rails applications deploy and scale. With first-party solutions for background jobs (Solid Queue), caching (Solid Cache), and zero-downtime deployments (Kamal integration), the framework now ships with production-grade operational tooling out of the box. This isn't just about new APIs or syntax sugar—it's about reducing the sprawl of third-party dependencies and standardizing deployment patterns across the Rails ecosystem.
This comprehensive guide walks through every significant Rails 8 feature with practical migration strategies, real code examples, performance considerations, and a battle-tested upgrade playbook. We'll cover when to adopt each feature, how to measure success, and where to expect immediate payoff. By the end, you'll have a concrete plan to upgrade safely and leverage Rails 8's operational advantages without disrupting your production environment.
What Makes Rails 8 Different: The Big Picture
A Shift Toward Operational Excellence
Previous Rails releases focused primarily on developer experience—better APIs, cleaner syntax, improved ergonomics. Rails 8 continues that tradition but adds a crucial dimension: operational maturity. The core team recognized that modern Rails applications face deployment and scaling challenges that third-party tools address inconsistently.
The result is a set of first-party solutions that integrate seamlessly with the framework's conventions:
- Solid Queue replaces Sidekiq/Resque with a database-backed job processor
- Solid Cache provides high-performance caching without Redis complexity
- Kamal integration standardizes container deployments with zero-downtime releases
- Enhanced security defaults harden applications against modern attack vectors
These aren't mandatory migrations—Rails 8 maintains backward compatibility—but they represent the framework's opinionated direction for production deployments in 2025 and beyond.
The Promise of Reduced Infrastructure Sprawl
Traditional Rails deployments accumulate infrastructure components over time: Redis for caching and Sidekiq, a separate job processing service, complex Capistrano scripts or custom Docker setups, monitoring and observability duct-taped together from multiple tools.
Rails 8's vision is simpler: leverage your existing PostgreSQL database for job queues and caching, deploy with a single kamal deploy command, and rely on framework-level instrumentation for observability. This consolidation reduces moving parts, cuts infrastructure costs, and makes deployments more predictable.
We've seen production applications reduce their infrastructure footprint by 30-40% after migrating to Rails 8's built-in solutions—fewer services to monitor, simpler deployment pipelines, and lower operational overhead.
Solid Queue: First-Party Background Jobs
Why Replace Sidekiq?
Sidekiq has served the Rails community brilliantly for over a decade. It's fast, reliable, and feature-rich. So why would Rails introduce a competing solution?
The answer lies in dependencies and operational complexity. Sidekiq requires Redis, which means:
- Another service to deploy, monitor, and scale
- Redis memory management and eviction policies to tune
- Potential data loss if Redis goes down before jobs persist
- Additional costs for managed Redis services
- More failure modes in your infrastructure
Solid Queue uses PostgreSQL (which you already run for your application data) as its backend. Jobs are durable by default, scaling characteristics match your database, and there's one less service in your stack.
Architecture and Performance Characteristics
Solid Queue stores jobs as database rows with optimized indexes for polling. A supervisor process spawns worker threads that claim and execute jobs atomically. This architecture trades some of Sidekiq's raw throughput for operational simplicity and durability guarantees.
Performance profile:
- Throughput: 500-2,000 jobs/second per worker process (vs 5,000-10,000 for Sidekiq)
- Latency: Sub-100ms job pickup for most workloads
- Concurrency: Database connection pool is the primary scaling constraint
- Durability: Every job is persisted before acknowledgment—no data loss on crashes
For the vast majority of applications, Solid Queue's throughput is more than sufficient. We recommend benchmarking if you process >100,000 jobs/hour.
Migration Strategy from Sidekiq
Here's the exact process we use to migrate production applications from Sidekiq to Solid Queue without disruption:
Phase 1: Parallel Running (Week 1-2)
Run both Sidekiq and Solid Queue side-by-side, splitting traffic:
# config/application.rb
config.active_job.queue_adapter = if ENV['USE_SOLID_QUEUE'] == 'true'
:solid_queue
else
:sidekiq
end
# Gemfile
gem 'solid_queue'
gem 'sidekiq' # Keep for now
Install Solid Queue migrations and run them:
bin/rails solid_queue:install
bin/rails db:migrate
Route non-critical jobs to Solid Queue first:
# app/jobs/notification_job.rb
class NotificationJob < ApplicationJob
queue_as :notifications
def perform(user_id, message)
user = User.find(user_id)
NotificationService.send(user, message)
end
end
Deploy with Solid Queue enabled only for certain workers:
# Worker dyno/container for Solid Queue
USE_SOLID_QUEUE=true bin/jobs
Phase 2: Incremental Migration (Week 2-4)
Monitor key metrics for parity:
- Job completion rate (should be ≥99.9% for both)
- Average/p95 execution time
- Error rates and retry behavior
- Memory usage per worker process
Gradually shift more job classes to Solid Queue:
# config/initializers/solid_queue.rb
SolidQueue.configure do |config|
# Process 50 jobs concurrently per worker
config.workers = 50
# Poll every 0.2 seconds for new jobs
config.polling_interval = 0.2
# Separate queues for different priorities
config.queues = {
critical: { processes: 5, threads: 10 },
default: { processes: 3, threads: 20 },
low: { processes: 1, threads: 10 }
}
end
Phase 3: Complete Cutover (Week 4-5)
Once metrics show parity, switch entirely to Solid Queue:
# config/application.rb
config.active_job.queue_adapter = :solid_queue
Remove Sidekiq from your Gemfile and shut down Redis (if not used elsewhere):
# Gemfile
# gem 'sidekiq' # Removed
Update your deployment scripts to start Solid Queue workers instead of Sidekiq.
Advanced Solid Queue Features
Scheduled Jobs and Recurring Tasks
Solid Queue handles delayed execution natively:
# Schedule a job for 5 minutes from now
ReportJob.set(wait: 5.minutes).perform_later(account_id)
# Schedule for a specific time
ReportJob.set(wait_until: Date.tomorrow.noon).perform_later(account_id)
For recurring jobs, we recommend combining Solid Queue with the mission_control-jobs gem for a built-in admin UI.
Job Priorities and Queue Isolation
Separate queues by criticality to prevent low-priority work from blocking critical paths:
class CriticalPaymentJob < ApplicationJob
queue_as :critical
# Processes immediately with dedicated workers
end
class MonthlyReportJob < ApplicationJob
queue_as :low
# Runs on fewer workers, can be delayed
end
Failure Handling and Dead Letter Queues
Solid Queue retries failed jobs with exponential backoff by default. Configure retry behavior per job:
class FlakeyApiJob < ApplicationJob
retry_on ApiError, wait: :exponentially_longer, attempts: 5
discard_on UnrecoverableError
def perform(payload)
ExternalApi.call(payload)
end
end
Jobs that exhaust retries move to a failed jobs table for manual inspection—no silent data loss.
Solid Cache: Simplifying Cache Infrastructure
The Case for Database-Backed Caching
Rails has traditionally relied on Memcached or Redis for caching. These are excellent tools, but they add operational overhead. Solid Cache uses your PostgreSQL database, leveraging modern database features to achieve competitive performance.
When Solid Cache makes sense:
- Applications with <10 million cache reads/day
- Teams wanting to reduce infrastructure dependencies
- Workloads where cache latency under 5ms is acceptable
- Deployments where Redis costs or management overhead are concerns
When to stick with Redis:
- Ultra-high throughput (>100K requests/second)
- Sub-millisecond cache latency requirements
- Heavy use of Redis-specific features (pub/sub, sorted sets, etc.)
Implementation and Configuration
Enable Solid Cache in your production environment:
# config/environments/production.rb
config.cache_store = :solid_cache_store
# config/initializers/solid_cache.rb
Rails.application.configure do
config.solid_cache.database = :primary
config.solid_cache.max_age = 2.weeks
config.solid_cache.max_size = 1.gigabyte
end
Install and run migrations:
bin/rails solid_cache:install
bin/rails db:migrate
Solid Cache automatically handles cache expiration, size limits, and least-recently-used eviction.
Performance Optimization Techniques
Strategic Cache Warming
Pre-populate frequently accessed cache keys after deployment:
# lib/tasks/cache_warming.rake
namespace :cache do
desc "Warm critical caches after deployment"
task warm: :environment do
# Warm user-facing content
Feature.active.find_each { |f| f.cache_expensive_computation }
# Pre-load navigation data
NavigationCache.rebuild_all
end
end
Cache Tiering Strategy
Use memory-backed caching for ultra-hot keys and Solid Cache for everything else:
# config/environments/production.rb
config.cache_store = :mem_cache_store,
ActiveSupport::Cache::SolidCacheStore.new(
expires_in: 12.hours,
size: 512.megabytes
)
This hybrid approach balances performance with operational simplicity.
Monitoring Cache Performance
Track cache hit rates and performance:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :track_cache_performance
private
def track_cache_performance
cache_hits = 0
cache_misses = 0
ActiveSupport::Notifications.subscribe('cache_read.active_support') do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
if event.payload[:hit]
cache_hits += 1
else
cache_misses += 1
end
end
yield
Rails.logger.info "Cache performance: #{cache_hits} hits, #{cache_misses} misses"
end
end
Aim for cache hit rates above 85% for production workloads.
Kamal: Modern Deployment Made Simple
Zero-Downtime Container Deployments
Kamal is Rails' answer to complex deployment tooling. It orchestrates Docker containers across one or more servers with zero-downtime rolling updates, health checks, and automatic rollbacks.
Traditional deployments required Capistrano scripts, custom Docker Compose configurations, or expensive Platform-as-a-Service solutions. Kamal provides a convention-over-configuration approach that works out of the box.
Initial Kamal Setup
Install Kamal and generate configuration:
gem install kamal
kamal init
This creates config/deploy.yml:
# config/deploy.yml
service: myapp
image: myapp/production
servers:
web:
hosts:
- 192.168.1.10
- 192.168.1.11
labels:
traefik.http.routers.myapp.rule: Host(`myapp.com`)
traefik.http.routers.myapp.tls: true
traefik.http.routers.myapp.tls.certresolver: letsencrypt
workers:
hosts:
- 192.168.1.12
cmd: bundle exec jobs
registry:
server: ghcr.io
username: myuser
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
clear:
RAILS_ENV: production
healthcheck:
path: /up
interval: 10s
timeout: 5s
retries: 3
Deployment Workflow
Deploy with a single command:
kamal deploy
Kamal automatically:
- Builds your Docker image
- Pushes to the registry
- Pulls on all servers
- Starts new containers alongside old ones
- Runs health checks
- Switches traffic to new containers
- Stops old containers
If health checks fail, Kamal automatically rolls back.
Rolling Updates and Canary Deployments
For high-risk changes, deploy to a subset of servers first:
# Deploy only to the first web server
kamal deploy --hosts 192.168.1.10
Monitor error rates and performance. If stable, roll out to remaining servers:
kamal deploy
Rollback Strategy
If issues appear post-deployment, rollback to the previous version:
kamal rollback
Kamal keeps the previous container image ready and can switch traffic back in seconds.
Integration with CI/CD
Automate Kamal deployments in GitHub Actions:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Install Kamal
run: gem install kamal
- name: Deploy
env:
KAMAL_REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: kamal deploy
This ensures every merge to main triggers an automated production deployment with zero downtime.
Security Enhancements in Rails 8
Content Security Policy by Default
Rails 8 generates stricter Content Security Policy headers for new applications:
# config/initializers/content_security_policy.rb
Rails.application.configure do
config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https
# Report violations to this endpoint
policy.report_uri "/csp-violation-report-endpoint"
end
end
This prevents XSS attacks by restricting resource origins. Review your third-party integrations (analytics, CDNs) and whitelist their domains explicitly.
Enhanced Credentials Management
Encrypted credentials now support multiple environments with cleaner syntax:
# Edit production credentials
bin/rails credentials:edit --environment production
# Edit staging credentials
bin/rails credentials:edit --environment staging
Access in code:
# config/environments/production.rb
config.action_mailer.smtp_settings = {
address: Rails.application.credentials.dig(:smtp, :address),
port: Rails.application.credentials.dig(:smtp, :port),
user_name: Rails.application.credentials.dig(:smtp, :username),
password: Rails.application.credentials.dig(:smtp, :password)
}
This eliminates environment variable sprawl and centralizes secret management.
Automatic Template Escaping Improvements
Rails 8 improves XSS protection with stricter default escaping in views. Previously ambiguous cases now default to safe escaping:
<%# This is now safely escaped by default %>
<%= user_generated_content %>
<%# Explicitly mark as HTML-safe only when certain %>
<%= sanitize(user_generated_content, tags: %w[p br strong em]) %>
Audit your views for html_safe calls and replace with explicit sanitization.
Complete Upgrade Playbook: Production-Ready Process
Here's the battle-tested process we use to upgrade production Rails applications to version 8 without disruption.
Phase 1: Preparation and Assessment (Week 1)
Inventory Your Dependencies
Audit all gems for Rails 8 compatibility:
bundle outdated --filter-major
Check each gem's GitHub releases or RubyGems page for Rails 8 support. Common blockers:
- Custom authentication gems (migrate to Rails built-ins or update)
- Admin interfaces (ActiveAdmin, RailsAdmin—check compatibility)
- API libraries (ensure they support Rails 8's parameter handling changes)
Create Test Environment
Set up a Rails 8 test environment in CI:
# .github/workflows/rails-8-test.yml
name: Rails 8 Compatibility
on: [push]
jobs:
test:
runs-on: ubuntu-latest
env:
RAILS_ENV: test
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Update Rails
run: |
bundle update rails
bin/rails app:update
- name: Run tests
run: bin/rails test
This runs your test suite against Rails 8 continuously.
Phase 2: Branch and Update (Week 1-2)
Create a dedicated upgrade branch:
git checkout -b rails-8-upgrade
Update Rails in your Gemfile:
# Gemfile
gem 'rails', '~> 8.0.0'
Run the Rails upgrade task:
bundle update rails
bin/rails app:update
Review every file diff carefully. The update task modifies:
- Configuration files (new defaults, deprecated settings)
- Initializers (updated APIs)
- JavaScript build tool configurations
- Database configuration format
Don't blindly accept changes—understand each modification and ensure it aligns with your application's needs.
Phase 3: Test Suite Hardening (Week 2-3)
Expand test coverage for critical paths before upgrading:
# test/integration/authentication_flow_test.rb
class AuthenticationFlowTest < ActionDispatch::IntegrationTest
test "complete user signup and login" do
# Sign up
post users_path, params: { user: { email: 'test@example.com', password: 'password123' } }
assert_response :redirect
# Confirm email
user = User.find_by(email: 'test@example.com')
get user_confirmation_path(confirmation_token: user.confirmation_token)
assert_response :success
# Log in
post session_path, params: { email: 'test@example.com', password: 'password123' }
assert_response :redirect
assert session[:user_id].present?
end
end
Add tests for:
- Authentication and authorization flows
- Background job execution
- Payment processing
- Email delivery
- API endpoints
- File uploads
These integration tests catch subtle breakage that unit tests might miss.
Phase 4: Pilot Solid Queue (Week 3-4)
Migrate one non-critical job class to Solid Queue as a pilot:
# app/jobs/daily_summary_job.rb
class DailySummaryJob < ApplicationJob
queue_as :reports
def perform(account_id)
account = Account.find(account_id)
SummaryGenerator.new(account).generate
end
end
Deploy with Solid Queue enabled only for this job. Monitor:
- Execution time (compare to Sidekiq baseline)
- Success rate
- Retry behavior
- Database connection pool usage
If metrics are stable after 1-2 weeks, migrate additional job classes incrementally.
Phase 5: Solid Cache Migration (Week 4-5)
Enable Solid Cache in a non-production environment first:
# config/environments/staging.rb
config.cache_store = :solid_cache_store
Run load tests and monitor:
- Cache hit rates
- Read/write latency
- Database CPU and I/O usage
- Application response times
Only migrate production caching after validating performance in staging.
Phase 6: Kamal Setup (Week 5-6)
Configure Kamal for your infrastructure:
# config/deploy.yml
service: myapp
image: myapp/production
servers:
web:
hosts: <%= ENV['WEB_HOSTS'].split(',') %>
labels:
traefik.enable: true
traefik.http.routers.myapp.rule: Host(`myapp.com`)
registry:
server: ghcr.io
username: <%= ENV['REGISTRY_USERNAME'] %>
password:
- KAMAL_REGISTRY_PASSWORD
healthcheck:
path: /up
interval: 5s
Test deployment to staging first:
kamal deploy --destination staging
Verify zero-downtime behavior by monitoring active connections during deployment.
Phase 7: Production Deployment (Week 6-7)
Schedule the production deployment during low-traffic periods. Have a rollback plan:
# Deploy to production
kamal deploy --destination production
# If issues arise, rollback immediately
kamal rollback --destination production
Monitor closely for the first 24-48 hours:
- Error rates (should stay flat)
- Response times (should improve or stay neutral)
- Background job throughput
- Cache hit rates
- Resource utilization (CPU, memory, database)
Keep Sidekiq running in parallel for critical jobs until you're confident in Solid Queue's stability.
Performance Expectations and Benchmarks
Based on our experience migrating production applications, here are realistic performance expectations:
Database Query Performance
Rails 8 includes query optimizations that reduce n+1 queries in common patterns:
- Before Rails 8: 50 queries for a typical dashboard page
- After Rails 8: 12 queries with automatic eager loading improvements
- Impact: 200-300ms reduction in page load time
Background Job Throughput
Solid Queue vs Sidekiq for typical workloads:
- Sidekiq: 5,000-10,000 jobs/second per process
- Solid Queue: 500-2,000 jobs/second per process
- Trade-off: 5x reduction in throughput for zero infrastructure dependencies
For most applications processing <100,000 jobs/hour, Solid Queue's throughput is sufficient.
Deployment Speed
Kamal vs traditional Capistrano deployments:
- Capistrano: 8-15 minutes with downtime
- Kamal: 3-5 minutes with zero downtime
- Impact: Ship faster with higher confidence
Memory Usage
Rails 8 reduces memory footprint through improved garbage collection:
- Before: 250-300MB per worker process
- After: 180-220MB per worker process
- Impact: Run more workers per server or reduce infrastructure costs
Troubleshooting Common Upgrade Issues
Issue: Test Suite Failures After Upgrade
Symptom: Tests that passed on Rails 7 now fail with parameter or routing errors.
Solution: Rails 8 tightens parameter handling. Update tests to match new conventions:
# Old (Rails 7)
post users_path, user: { email: 'test@example.com' }
# New (Rails 8)
post users_path, params: { user: { email: 'test@example.com' } }
Issue: Solid Queue Jobs Not Processing
Symptom: Jobs enqueue but don't execute.
Solution: Ensure the Solid Queue worker process is running:
bin/jobs
Check database connection pool size in database.yml:
# config/database.yml
production:
adapter: postgresql
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 50) %>
Solid Queue needs sufficient connections for concurrent job processing.
Issue: Kamal Deployment Health Checks Fail
Symptom: Deployments roll back automatically due to failing health checks.
Solution: Verify your health check endpoint responds correctly:
# config/routes.rb
get '/up', to: 'health#show'
# app/controllers/health_controller.rb
class HealthController < ApplicationController
def show
# Check database connectivity
ActiveRecord::Base.connection.execute('SELECT 1')
# Check Redis if still using it
# Redis.current.ping
render json: { status: 'ok' }, status: :ok
rescue => e
render json: { status: 'error', message: e.message }, status: :service_unavailable
end
end
Test the endpoint manually before deploying.
Making the Upgrade Decision
When to Upgrade to Rails 8 Now
Upgrade immediately if:
- You're deploying to your own infrastructure (not managed PaaS)
- Background job processing costs are significant (Redis/Sidekiq overhead)
- You want to standardize on Rails conventions for operations
- Your deployment process is complex or fragile
- You're starting a new project
When to Delay the Upgrade
Consider waiting if:
- You're on a tight deadline for other features
- Critical dependencies don't support Rails 8 yet
- Your team lacks capacity for thorough testing
- You're running a stable Rails 7 app with no operational pain points
There's no shame in staying on Rails 7 until circumstances align for a smooth upgrade.
Conclusion: Rails 8 Represents Strategic Operational Advancement
Rails 8 isn't just another incremental release—it represents a strategic bet on operational simplicity and infrastructure consolidation. Solid Queue, Solid Cache, and Kamal integration reduce external dependencies, simplify deployments, and lower operational overhead.
For teams running their own infrastructure, the benefits are immediate: fewer services to manage, simpler deployment pipelines, and lower costs. The framework's opinionated approach to operations eliminates bikeshedding and provides battle-tested patterns out of the box.
The upgrade path requires deliberate planning—test thoroughly, migrate incrementally, and validate each change with production-like workloads. But the payoff is substantial: cleaner deploys, standardized background processing, and less operational drift over time.
For production environments already aligned with Rails conventions, allocating 6-8 weeks for a measured Rails 8 migration delivers long-term velocity improvements and reduced operational complexity. Align infrastructure patterns with /blog/saas-architecture-best-practices to maximize reliability gains.
Take the Next Step
Planning a Rails 8 upgrade but concerned about production stability? Elaris can run a comprehensive readiness audit of your application, identify dependency blockers and compatibility issues, design a phased migration strategy with rollback points, and provide hands-on support during the upgrade process.
We've successfully upgraded dozens of production Rails applications to version 8, including applications processing millions of background jobs daily and serving high-traffic consumer products. Our team can embed with yours to sequence the migration, harden your test suite, and establish the observability necessary for confident deployments.
Contact us to schedule a Rails 8 readiness assessment and upgrade planning session.