Maintenance

Upgrading an Old Ruby on Rails Application Safely, One Version at a Time

An old Rails application is usually a sign of success, until the host drops its Ruby version or a gem stops getting security fixes. Here is the staged approach we use: inventory first, tests before upgrades, one minor version at a time, and a plan for the usual sticking points.

Workshop wall with hand tools hung in neat rows above a workbench

A Rails application that has been running for eight or ten years is usually a sign of success. It holds years of business rules, it pays the bills, and nobody wants to touch it. The trouble is that it is often several major versions behind, on a Ruby the host no longer supports, with gems that stopped receiving security fixes long ago. At some point the upgrade stops being optional, and the question becomes how to do it without breaking the thing the business depends on.

Taking over and upgrading existing applications is a regular part of our Ruby on Rails development work. The approach below is the one we use: slow, staged and deliberately unexciting.

First, Find Out Where You Are

Before changing a line, take an inventory:

  • Versions: Ruby, Rails, Bundler, the database and its adapter, and Node if the asset build uses it.
  • Gems: run bundle outdated for the full picture and bundler-audit for known vulnerabilities. Mark each gem as maintained, abandoned or forked.
  • Monkey patches: anything in config/initializers or lib that reopens Rails or gem classes. These break silently on upgrade.
  • Deprecation warnings: the current version is already telling you what the next one will remove. Collect them from the test run and the production logs.
  • Infrastructure: how the app is deployed, which job system it uses, and anything pinned at operating-system level, such as an old ImageMagick or OpenSSL.

The inventory is also the basis of an honest estimate. An application one major version behind with maintained gems is a few weeks of careful work. One on Rails 4 with a dozen abandoned gems is a project, and should be planned as one.

Tests Before Upgrades

An upgrade is only as safe as your ability to notice what it broke. Many inherited applications have a test suite that has not passed in years, or hardly any tests at all. Fixing that comes first.

We do not aim for full coverage before starting; that could take longer than the upgrade. We aim for coverage of what would hurt: request specs for the critical journeys (sign-up, checkout, invoicing, whatever earns money) and characterisation tests around the business rules that are hardest to reason about. A characterisation test records what the code does today, right or wrong, so any change in behaviour shows up as a failure. SimpleCov shows where the gaps are.

We took the same approach outside Rails when we upgraded a trade supplier's CodeIgniter order desk to PHP 8: the trade-discount logic was covered by tests before anything was touched, so the upgrade could not silently change prices. Different framework, same principle.

One Version at a Time

It is tempting to jump straight from Rails 5 to Rails 8. Do not. Each release removes things the previous one deprecated, and the official upgrade guides are written step by step. Skipping steps means facing every removal at once, without the deprecation warnings that would have pointed the way.

The rhythm for each step:

  1. Fix every deprecation warning on the current version.
  2. Bump Rails to the next minor version and update the gems that require it.
  3. Run the app:update task and review the generated configuration changes by hand rather than accepting them wholesale.
  4. Get the tests passing, then deploy to staging and production.
  5. Switch on the new framework defaults one at a time using the generated defaults file, then update load_defaults.

Deploying after every step matters. A problem found in production after one minor version is easy to trace. A problem found after five is a week of bisecting.

Ruby and Rails have to leapfrog. Each Rails release sets a minimum Ruby version, and older Rails releases do not support the newest Rubies, so the path alternates: upgrade Ruby as far as the current Rails allows, then Rails as far as that Ruby allows, and repeat. Check the supported combinations in the release notes before planning the sequence.

For larger applications, dual booting helps. The app runs against both the current and the next Rails version from one branch, switched by an environment variable, so the upgrade proceeds in small merged pull requests rather than a long-lived branch that drifts away from everyone else's work.

The Usual Sticking Points

  • Autoloading. Rails 6 introduced the Zeitwerk autoloader and Rails 7 removed the classic one. Files whose names do not match the class they define, common in old code, need renaming. The zeitwerk:check task lists them.
  • Front-end assets. Webpacker has been retired, so apps that used it move to jsbundling-rails, import maps or a separate build. This is often the most time-consuming single step, and it can be done separately from a Rails bump.
  • Abandoned gems. Paperclip, once the standard for file attachments, was deprecated in favour of Active Storage, and old authentication or admin gems may need replacing or forking. Budget for each one individually.
  • Background jobs. Check that jobs queued by the old version can still be processed by the new one during the deployment window.
  • Behaviour changes in very old apps, such as the move from attribute protection to strong parameters, where code still runs but no longer does what it used to.

Upgrade, Migrate or Rewrite?

A rewrite throws away years of encoded business rules and replaces them with new bugs. For a reasonably structured Rails app, upgrading incrementally is nearly always cheaper and safer. Where parts of the application are genuinely beyond saving, replace them one module at a time behind the same URLs, which is how the CodeIgniter order desk moved to Laravel. Our article on whether Rails is still a good choice covers the longer-term question of staying on the framework, and the same decision for CodeIgniter shows how we weigh it in another ecosystem.

New Rails work does not have to become tomorrow's rescue job. The field-service quoting SaaS we built in Rails has RSpec tests, Sidekiq for background work and Docker for deployment, the foundations that keep future upgrades routine rather than frightening.

Keep It Current Afterwards

The cheapest upgrade is the one done a little at a time. Once an application is current, a maintenance plan that applies Ruby, Rails and gem updates every month or two keeps it there, with the test suite as the safety net. Moving the app to a containerised deployment through our Docker and DevOps service also removes the "it only runs on that one server" problem that makes Ruby upgrades so nerve-racking.

If you have a Rails application nobody wants to touch, send us its Gemfile.lock and the Ruby version it runs on. From those two things we can tell you how far behind it is, which gems will cause trouble and roughly how many steps the upgrade will take.

Frequently Asked Questions

How long does it take to upgrade an old Rails application?

It depends far more on the gap and the gems than on the size of the codebase. An app one major version behind with maintained dependencies and a working test suite can often be brought current in a few weeks. An app several major versions behind, with abandoned gems and no tests, needs time for test coverage first and may take a few months of staged releases. An inventory of versions and gems gives a reliable estimate.

Is it risky to keep running an unsupported version of Rails?

Yes, and the risk grows over time. Unsupported Rails and Ruby versions stop receiving security patches, so newly discovered vulnerabilities stay open. Hosting platforms eventually drop old Ruby versions, gems stop supporting them, and security questionnaires from customers or insurers increasingly ask about it. The application may run perfectly well for years, but every month makes the eventual upgrade larger.

Do we have to stop feature development during a Rails upgrade?

Not usually. With dual booting, the application runs on both the current and next Rails version from the same branch, so upgrade fixes are merged in small pull requests alongside normal feature work. Each step is deployed on its own. The main discipline needed is that new code is written to work on both versions until the switch is complete, which a CI job running the test suite against each version enforces.

What if our Rails application has almost no tests?

Then the first phase of the upgrade is writing them, but selectively. We start with request specs for the journeys that earn money or would cause the most damage if broken, then characterisation tests around complicated business rules. That is usually enough to upgrade with confidence. Full coverage is not required up front, and the tests remain useful long after the upgrade is finished.

Can we upgrade Ruby without upgrading Rails?

Only up to a point. Each Rails version supports a range of Ruby versions, so you can usually move Ruby forward a step or two on your current Rails, which is often a good first move because newer Rubies are faster and better supported. Beyond that range, Rails itself has to move. Planning the sequence of Ruby and Rails steps together avoids getting stuck on a combination that neither supports.

How We Can Help

Services Related to This Article

The work we do with the technologies discussed above — with pricing, process and real examples on each page.

  • Ruby on Rails Development

    Rails applications and MVPs built quickly and cleanly — Hotwire interfaces, background jobs, Stripe billing and upgrades of existing Rails code.

  • Website Maintenance & Support

    Proactive updates, backups, monitoring and on-demand fixes that keep your website secure, fast and always online.

  • Docker & Kubernetes

    Applications packaged in Docker, deployed with CI/CD, and run on Kubernetes when they need to scale — with monitoring, secrets and rollbacks built in.

In Practice

Case Studies on This Topic

Projects where we put these ideas to work, each with a live demo.

  • Tools Workshop — Quotely
    • Ruby on Rails
    • Hotwire (Turbo & Stimulus)
    • PostgreSQL

    SaaS Product Development

    Field-Service Quoting SaaS

    A Rails MVP taken from idea to paying subscribers in ten weeks, with Stripe billing and Hotwire interactivity.

Keep Reading

  • Dental treatment chair and equipment in a bright clinic room

    Moving Hosting and DNS Without Downtime: The Order That Works

    When a host move goes wrong, it is rarely the copying of files. It is DNS: a forgotten record, a certificate that could not be issued, a dropped mail record or a cached address. Here is the order we follow, from record inventory and TTLs to cut-over and rollback.

  • Pastel ceramic cups, bowls and plates stacked on a shelf

    Moving a Retailer's Server Room to Azure: What Changes on Monday Morning

    For a small or mid-sized retailer, moving from a server room to Azure is less about technology than about what changes for the business afterwards: who looks after backups, how shops and head office reach their systems, what the monthly bill looks like and what still needs a person.

  • Hiker standing on a mountain ridge looking out over a green valley

    Building a Tour Booking Platform: Availability, Group Sizes, Deposits and Supplier Sync

    Tour operators sell dated departures with limited seats, minimum numbers, deposits and balances, and suppliers behind every itinerary. Here is how we model departures, stop double-bookings, take deposits without chasing balances and decide how much supplier integration is worth building.

Let’s Connect

Let’s Build Something Amazing Together

Share your project details and our experts will get back to you within one business day with the best solution — free consultation, no obligation.