Migrating legacy systems to contemporary stacks is an inevitability for CTOs and technically inclined CEOs managing scalable operations. Beyond simply maintaining security updates, these upgrades offer strategic opportunities for application modernisation, performance optimisation, and infrastructure future-proofing. We recently undertook a small but technically intricate project of transitioning multiple services from Heroku-20 to Heroku-24 while upgrading Ruby from version 2.7.6 to 3.3.4 — a classic case of a Heroku Ruby Migration. Below, we detail the methodical approach that enabled us to deliver this technically complex project ahead of schedule with zero downtime or disruptions.
The Context
Heroku had announced the end-of-life for its Heroku-20 stack effective 30 April 2025; builds and deployments for apps on Heroku-20 would no longer be supported after 1 May. Our client’s organisation’s critical services were operating on this retiring stack, creating an immediate need to migrate to Heroku-24 to ensure uninterrupted functionality and continued security updates. However, Heroku-24 mandated the use of Ruby 3.3.4.
The project encompassed upgrading the following five key services:
- Legacy Route Redirection Service: A lightweight redirect service for deprecated URLs
- User Authentication and Access Management Service: An authentication system managing user credentials and access protocols
- Marketing Website: A promotional content website with limited automated tests
- Multi-Role User Frontend Service: A large frontend application used by the end users and back-office administrators
- Backend Service: A complex backend orchestrating order fulfilment, email notifications, and background processes.
Each of these presented unique technical and operational challenges in testing, dependency resolution, and ensuring compatibility.
Challenges We Faced
While necessary because Heroku-24’s features promised long-term benefits, the migration was fraught with complications typical of legacy system upgrades. Below, we outline the key challenges and our tailored solutions:
1. Dependency Management
- Challenge: Many services relied on outdated gems or integrations, such as a deprecated WordPress gem incompatible with Ruby 3.3.4.
- Solution: We forked and updated the WordPress gem to maintain compatibility with Ruby 3.3.4, preserving existing integrations and avoiding disruptions.
2. Variability in Test Coverage
- Challenge: Some services, such as Multi-Role User Frontend Service and Marketing Website, lacked adequate automated tests, making it harder to identify issues post-upgrade.
- Solution: We implemented rigorous manual testing procedures for critical flows, complemented by focused new unit and integration tests to mitigate gaps.
3. Gem Updates and Incompatibilities
- Challenge: Essential components like Latent Dirichlet Allocation (LDA) were tied to gems unavailable in Ruby 3.3.4.
- Solution: We carefully replaced legacy tools with stable, supported alternatives, conducting exhaustive validation to ensure robustness.
4. Integration Challenges
- Challenge: Complex interdependencies, such as plugins within apps like Backend Service required delicate handling to balance stability with upgrades.
- Solution: We maintained specific dependencies in older versions where logic constraints applied while updating others for compatibility and performance improvements.
5. Orchestration Across Multiple Services
- Challenge: Upgrading five interdependent services required careful timing and coordination to minimise risks.
- Solution: We adopted a phased approach, starting with simpler applications to gain insights and gradually scaling up to larger ones.
 Our Process
To ensure a smooth migration, we adhered to a structured process:
1. Pre-Migration Analysis and Planning
We evaluated Heroku-22 and Heroku-24 stacks, ultimately selecting Heroku-24 for its extended support horizon and compatibility with Ruby 3.3.4.
We estimated effort and categorised services by complexity to allocate appropriate resources: smaller apps required 1–2 hours, while larger services needed 10–14 hours.
2. Incremental Ruby Upgrade Phases
We prioritised smaller services like Legacy Route Redirection Service and Legacy Route Redirection Service for early wins. Their upgrades exposed any hidden challenges in dependencies and established patterns for subsequent work.
We then tackled medium-complexity services, such as Marketing Website, before finally addressing the highly intricate upgrades for Multi-Role User Frontend Service and Backend Service, applying lessons learned to mitigate risks.
3. Thorough Testing
We conducted robust testing in staging environments to verify compatibility, covering pivotal workflows such as user registration, order processing, password resets, and WordPress integration.
We allocated resources for manual testing in apps with limited automated coverage, ensuring that all significant flows functioned correctly.
4. Efficient Coordination and Execution
We implemented parallel streams for development and QA to expedite timelines without compromising diligence. We adopted agile iteration cycles, immediately addressing surfacing defects to avoid downstream delays and ensure a smooth path to production.
5. Production Deployment and Monitoring
After rigorous testing and client verification, we deployed changes incrementally to production, enabling real-time validation and quick remediation. We monitored for anomalies post-deployment, paying special attention to dependencies prone to instability, such as custom plugins.
The Outcome
The migration delivered the following outcomes:
- Successful Platform Transition: All five services successfully migrated to Heroku-24, fully compatible with Ruby 3.3.4, without any downtime or disruptions in less than 2 weeks.
- Dependency Modernisation: We replaced outdated gems and tools and upgraded without disrupting existing integrations.
- Operational Improvements: We enhanced performance, security, and readiness for future upgrades.
This migration ensured our client’s compliance with Heroku’s stack deprecation deadlines and bolstered their technical foundation, giving them a competitive edge in scalability and stability.
Conclusion
Our key takeaways from this exercise are as follows:
- Incremental upgrades minimise operational risks and allow early troubleshooting, reducing risks in complex migrations
- Selective dependency updates mitigate disruptions while unlocking performance gains
- Good test coverage helps significantly, but when it is lacking, then extending automation and prioritising manual testing also pays dividends
- Deep knowledge of the stack (Heroku and Ruby ecosystems in this particular case) helps to expedite migration.
This project underscores the transformative potential of well-orchestrated migrations. By combining technical rigour with strategic foresight, we ensured uninterrupted client operations and delivered measurable upgrades ahead of the deadline.
If you’re facing the daunting task of modernising your legacy stacks, let’s collaborate. With our proven expertise, we can streamline your transformation and position your organisation for sustained success.