
Beyond the Version Number: How We Modernized a Legacy Rails 6 App into a Future-Ready Rails 8 Platform
Table of Contents
ToggleWhen we decided to modernize InspectDate, our loan approval platform connecting with trusted vendors, the goal wasn’t just to “upgrade Rails.” It was to future-proof our system, improve performance, simplify deployments, and ensure long-term maintainability.
But as anyone who’s touched a legacy Rails app knows, migrating from Rails 6 → Rails 8 is less of an upgrade and more of a journey, through deprecations, segfaults, and the occasional existential crisis.
This is the story of how we went from a tangled asset pipeline to a clean, modern architecture, and what business leaders can take away from it.
The Business Case for Modernization
The project’s objective was clear:
Objective: Modernize the InspectDate stack to improve performance, maintainability, and deployment efficiency.
The legacy setup was showing its age:
- Ruby 2.5.1 struggling with memory management
- Rails 6 running on outdated libraries
- A brittle Sprockets + Sass-Rails asset pipeline
- Inconsistent deployment environments
Every incremental update felt risky. Our engineers were spending more time patching the past than building the future. So, we committed to a strategic modernization, not just patchwork.
Chapter 1: The Rails 6 → 8 Upgrade Begins
The upgrade began simply enough:
# Target versions
Ruby: 2.5.1 → 3.3.3
Rails: 6 → 8.0.1We wanted to leverage Rails 8’s improved concurrency, Hotwire support, and Ruby 3.3’s JIT (Just-In-Time) compiler for performance.
Our app, however, was still wired with the classic:
gem 'sass-rails'
gem 'sprockets-rails'
gem 'sprockets'and assets managed via:
@import "variables";
@import "header";
It worked… until it didn’t.Chapter 2: Enter Docker & The Great Segfault
As we Dockerized the app to standardize environments and streamline deployments, a mysterious issue emerged.
💥 Segmentation fault (core dumped)Every time we ran bundle install or compiled assets, Docker would crash.
After hours of debugging, we traced it to sass-rails, which depended on libsass, an outdated C library incompatible with Ruby 3.3’s YJIT.
Our setup was too modern for its own legacy stack.
Even switching to sassc-rails didn’t help, the segfault persisted. That’s when the realization hit:
The real issue wasn’t the segfault. It was the outdated ecosystem around it.
Chapter 3: Saying Goodbye to Sprockets
The turning point came when we accepted a hard truth, Sprockets was done.
Rails core had already moved on, recommending Propshaft, a cleaner, faster replacement.
So, we removed the old asset pipeline:
# Gemfile (removed)
gem 'sass-rails'
gem 'sprockets-rails'
gem 'sprockets'And added:
gem 'propshaft'
Then updated our config/application.rb:
# require "sprockets/railtie"
require "propshaft/railtie"Just like that, we freed the application from years of baggage.
Chapter 4: Rebuilding CSS with Dart Sass + ESBuild
Next up: CSS compilation.
Since Dart Sass is now the reference implementation (and no longer available as a Ruby gem), we turned to Yarn and ESBuild.
We rewrote the old imports:
// Old Sass-Rails style
@import "variables";
@import "header";to the new Dart Sass syntax:
// New Dart Sass style
@use "sass:math";
@use "variables" as *;
@use "header";
padding: math.div(10px, 2);This alone eliminated several warnings and improved build times.
The pipeline was now language-agnostic, faster, and aligned with Rails 8’s philosophy.
Chapter 5: The Hidden Villain, Page-Specific Styles
Then came an unexpected blocker.
Our app used page-specific stylesheets, loaded dynamically:
= stylesheet_link_tag "pages_scss/#{controller_name}-#{action_name}", media: "all"But Propshaft didn’t auto-load or support glob imports, and our single application.scss wasn’t cutting it anymore.
We needed a modern equivalent to per-page SCSS, without reverting to the old Sprockets magic.
Chapter 6: ESBuild to the Rescue
The solution came from thinking like a builder, not a maintainer.
Instead of trying to replicate Sprockets’ behavior, we used ESBuild to compile each SCSS file individually.
Our structure looked like this:
app/assets/stylesheets/pages_scss/
├── users-show.scss
├── orders-index.scss
└── …And our build process (yarn build) handled each file:
yarn build
RAILS_ENV=production bin/rails assets:precompileEach page now had its own optimized CSS bundle, just like before, but with modern tooling, zero segfaults, and faster builds.
Chapter 7: Cleanup & Final Polish
Once everything compiled successfully, we cleaned house:
# Commands
yarn build
bin/dev
RAILS_ENV=production bin/rails assets:clobber
RAILS_ENV=production bin/rails assets:precompileAnd removed the remnants of the old world:
Deleted app/assets/config/manifest.js
Removed all //= require directives
Migrated fonts & images to Propshaft paths
Reorganized SCSS into base/, components/, and layout/The result? A leaner, modular, and future-proof front-end system.
Outcomes
| Aspect | Before | After |
|---|---|---|
| Ruby Version | 2.5.1 | 3.3.3 |
| Rails Version | 6 | 8.0.1 |
| Asset Pipeline | Sprockets + Sass-Rails | Propshaft + Dart Sass |
| Deployment | Manual environments | Dockerized consistency |
| Performance | Slow, memory-heavy | Faster JIT compilation |
| Maintainability | Legacy dependencies | Clean, modern stack |
Highlights:
- Migrated from deprecated Ruby Sass to Dart Sass
- Eliminated segfaults and deprecated gems
- Improved build and runtime performance
- Streamlined deployment with Docker
- Created a future-ready Rails stack
Lessons Learned (For Business Decision-Makers)
Upgrading a legacy app isn’t just a technical move, it’s a strategic business investment.
Here’s what it taught us:
- Technical debt compounds silently.
Old dependencies don’t just slow developers down, they add operational risk. - Modernization should be holistic, not incremental.
Fixing one gem won’t solve an outdated ecosystem. Update the architecture, not just the version. - Tooling simplicity pays long-term dividends.
Propshaft and ESBuild reduced build complexity, improving onboarding and maintenance costs. - Docker is more than deployment, it’s stability.
Environment consistency alone saved hours of debugging. - Performance gains are real.
Ruby 3.3’s JIT compiler and cleaner asset handling made the app snappier and lighter.
In Retrospect, A Detox, Not Just an Upgrade
This migration wasn’t just about keeping up with Rails, it was about shedding the weight of the past.
By replacing Sprockets with Propshaft, Ruby Sass with Dart Sass, and manual deployments with Docker, we built an application that’s cleaner, faster, and easier to evolve.
Modern Rails prefers simplicity.
Don’t just upgrade your code, upgrade your mindset.
Final Tech Stack
| Layer | Technology |
|---|---|
| Language | Ruby 3.3.3 |
| Framework | Rails 8.0.1 |
| Assets | Propshaft + Dart Sass + ESBuild |
| Deployment | Docker |
| Infrastructure Goal | Speed, Maintainability, Future-readiness |
In business terms?
We didn’t just upgrade a Rails app, we de-risked our product, future-proofed our technology, and gave our engineering team the clarity to build faster.
Read the full breakdown of the Rails 6 → Rails 8 modernization journey here — a deep-dive into the migration challenges, architecture decisions, and performance wins, originally published by Mitesh Thakkar on Medium,
Related Article





