Ruby on Rails 8 Upgrade Guide: Zero-Downtime Migration from Webpacker & Sidekiq to Propshaft & Solid Queue
A comprehensive 2026 engineering playbook for upgrading legacy Rails 6/7 applications to Rails 8 with zero downtime: eliminating Webpacker for Propshaft, Sidekiq for Solid Queue, and setting up Kamal 2.
For engineering teams maintaining legacy Ruby on Rails applications built between 2018 and 2023, the infrastructure footprint has accumulated significant operational drag: fragile Webpacker/Node build chains, memory-heavy Redis clusters for Sidekiq background jobs and ActionCable WebSockets, and complex container orchestration scripts. Upgrading to Rails 8 is not merely a version bump—it is an opportunity to compress your entire production infrastructure into a lean, self-contained system running modern SSD-backed databases and Kamal 2 container deployments.
Having executed end-to-end legacy modernizations on platforms like ZenHQ (Shopify order automation engine) and ProcessKit (multi-tenant workflow SaaS), we have developed a battle-tested, zero-downtime migration protocol. This guide outlines the exact 5-phase migration blueprint.
Watch: Rails 8 Architecture & The "One Box" Vision
Watch David Heinemeier Hansson (DHH) introduce the core philosophy behind Rails 8, Kamal 2, and the Solid backend engines:
1. The Rails 8 Modernization Matrix
Before modifying any configuration files, assess what components in your current stack are being deprecated and replaced:
| Component | Legacy Stack (Rails 6/7) | Rails 8 Default | Operational Impact |
|---|---|---|---|
| Asset Pipeline | Webpacker / Shakapacker / Sprockets | Propshaft + Importmaps / JSBundling | 10x faster asset compilation, no Webpack config bloat, pure HTTP/2 asset fingerprinting. |
| Background Processing | Sidekiq + Redis instance | Solid Queue (PostgreSQL / SQLite) | Eliminates Redis bill and memory pressure; database transactions ensure zero phantom jobs. |
| Key-Value Caching | Redis Cache Store / Memcached | Solid Cache | Gigabytes of fast SSD disk cache replacing expensive RAM instances. |
| WebSockets / PubSub | ActionCable on Redis | Solid Cable | PostgreSQL LISTEN/NOTIFY or polling table without separate message broker. |
| Deployment | Capistrano / Heroku / Custom AWS ECS | Kamal 2 + Thruster | Zero-downtime rolling Docker deploys to any bare metal or cloud VM with built-in asset caching. |
2. Phase 1: Pre-Upgrade Audit & Ruby 3.3/3.4 Compatibility
Do not jump directly from Rails 6.1 or 7.0 to 8.0 in a single step. The proven upgrade trajectory is:
Rails 6.1 → Rails 7.0 → Rails 7.1 → Rails 7.2 → Rails 8.0
Ensure your production Ruby runtime is at least Ruby 3.2+ (ideally Ruby 3.3 or 3.4 with YJIT enabled for up to 25% CPU performance gains). Run the deprecation analyzer in your test suite:
# config/environments/test.rb
Rails.application.configure do
# Raise errors on deprecation warnings so your test suite catches breaking changes
config.active_support.deprecation = :raise
config.active_support.disallowed_deprecation = :raise
end
3. Phase 2: Migrating from Webpacker to Propshaft + JSBundling
Webpacker was retired in Rails 7, yet hundreds of production apps still carry legacy webpacker.yml configs. Propshaft is designed to replace Sprockets and Webpacker by doing only one thing: asset path mapping and cache-busting digest generation.
Step 2.1: Gemfile Updates
# Gemfile
# Remove: gem 'webpacker'
# Remove: gem 'sprockets-rails'
# Add:
gem 'propshaft'
gem 'jsbundling-rails' # if you use React/Vue/TypeScript
gem 'cssbundling-rails' # if you use Tailwind or Bootstrap
Step 2.2: Adjusting Asset References in Views
Propshaft does not transform or bundle files—it expects bundled files to sit in app/assets/builds. Replace legacy javascript_pack_tag helpers with standard javascript_include_tag:
<!-- app/views/layouts/application.html.erb -->
<!-- Old: <%= javascript_pack_tag 'application', 'data-turbo-track': 'reload' %> -->
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
4. Phase 3: Zero-Downtime Migration from Sidekiq to Solid Queue
The single greatest operational hazard during a background queue migration is losing in-flight jobs or starving long-running batches. Here is how we execute the switch without dropping a single job.
Step 3.1: Dual-Running Sidekiq and Solid Queue
Install solid_queue while keeping sidekiq installed in your Gemfile:
# Gemfile
gem 'solid_queue'
gem 'sidekiq' # Keep during migration phase
Generate and run the Solid Queue database migrations:
bin/rails solid_queue:install
bin/rails db:migrate
Step 3.2: Configure Multi-Database Queue Isolation (PostgreSQL)
For high-throughput applications, isolate Solid Queue tables in a dedicated queue database pool to prevent lock contention on your primary application records:
# config/database.yml
production:
primary:
<<: *default
database: myapp_production
queue:
<<: *default
database: myapp_production_queue
migrations_paths: db/queue_migrate
Step 3.3: Configuring the Solid Queue Supervisor
# config/queue.yml
production:
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: [critical, default]
threads: 5
processes: 2
polling_interval: 0.1
- queues: [mailers, low_priority]
threads: 3
processes: 1
polling_interval: 1
Step 3.4: Drain the Sidekiq Queues
Switch your active job queue adapter to Solid Queue in application config:
# config/environments/production.rb
config.active_job.queue_adapter = :solid_queue
All new jobs will now queue into PostgreSQL via Solid Queue. Keep your Sidekiq workers running in production for 24–48 hours until Sidekiq's Redis queue depth hits exactly zero, after which you can safely terminate the Sidekiq process and decommission your Redis cluster.
5. Phase 4: Activating Solid Cache and Solid Cable
With Redis removed from background jobs, you can also replace Redis caching and WebSocket pubsub:
# config/environments/production.rb
config.cache_store = :solid_cache_store
config.action_cable.mount_path = "/cable"
# config/cable.yml
production:
adapter: solid_cable
connects_to:
database:
writing: primary
6. Phase 5: Production Deployment with Kamal 2 and Thruster
Rails 8 includes Thruster, an HTTP/2 proxy that sits in front of Puma inside the production Docker container, providing instant gzip/brotli compression and asset caching directly from disk.
# config/deploy.yml
service: my-rails8-app
image: registry.digitalocean.com/my-org/my-rails8-app
servers:
web:
hosts:
- 159.65.210.45
labels:
traefik.http.routers.app.rule: Host(`app.techvinta.com`)
traefik.http.routers.app.tls.certresolver: letsencrypt
job:
hosts:
- 159.65.210.45
cmd: bin/jobs
env:
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
Deploy with a single command:
kamal setup && kamal deploy
7. Infrastructure Cost Comparison: Legacy Rails vs Rails 8
Here is what happens to monthly hosting costs when consolidating a 100,000-user SaaS application from traditional Redis/Sidekiq/Node infrastructure to Rails 8 on standard VPS instances:
| Service Component | Legacy Stack (AWS / Heroku) | Rails 8 + Kamal Stack | Annual Savings |
|---|---|---|---|
| Managed Redis (Sidekiq + Cable) | $120 / mo | $0 / mo (Solid Queue in DB) | $1,440 / yr |
| Web & Worker Compute Instances | $350 / mo (4 dynos/instances) | $80 / mo (Single VPS / Hetzner) | $3,240 / yr |
| Third-Party Sidekiq Pro License | $99 / mo | $0 / mo (Solid Queue native) | $1,188 / yr |
| Total Annual Spend | $6,828 / year | $960 / year | Save $5,868 / year (85% reduction) |
Estimate your own specific hosting savings using our free Rails Deployment Cost Calculator.
Frequently Asked Questions
Can Solid Queue handle millions of jobs per day compared to Sidekiq?
Yes. 37signals processes millions of daily jobs on Solid Queue across Basecamp and HEY. Because Solid Queue uses modern transactional databases with skip-locked queries (FOR UPDATE SKIP LOCKED), it scales effortlessly on modern NVMe SSDs without memory leaks.
Do I need to rewrite my existing ActiveJob worker classes?
No. Solid Queue is a native ActiveJob adapter. Any job inheriting from ApplicationJob or ActiveJob::Base works immediately without changing your business logic.
Can I upgrade directly from Rails 6.0 to Rails 8?
We advise against skipping major versions. The safest path is upgrading step-by-step through each minor release (6.0 → 6.1 → 7.0 → 7.1 → 7.2 → 8.0), resolving deprecation warnings and passing your full test suite at each stage.
How long does a typical legacy Rails upgrade take?
A medium-sized Rails application (20k–50k lines of code) typically takes 2 to 4 weeks for a full audit, dependency upgrade, asset migration to Propshaft, and Solid Queue cutover with zero downtime.
Where can I find dedicated Rails upgrade support?
Learn more about our dedicated US engineering teams on our Hire Rails Developers USA page or explore our full suite of Ruby on Rails Development Services.
Planning a Zero-Downtime Rails 8 Upgrade?
Our senior Rails engineers have performed dozens of mission-critical version upgrades and legacy modernizations. Get a comprehensive codebase audit and fixed-price upgrade roadmap within 48 hours.
Request Codebase Audit & Estimate →