Engineering
Migrating a production platform from Symfony 5.4 to Symfony 8
3 min read
Migrating a production platform from Symfony 5.4 to Symfony 8
Context
MasMusculo/MMfit is a gym-management platform built on Symfony, used every day by real gyms to manage memberships, bookings and billing. It was running on Symfony 5.4, a version that was heading towards end of support, on top of a codebase that had accumulated years of shortcuts typical of a product that grew faster than its tooling.
Jumping four major versions (5.4 → 6 → 7 → 8) on a codebase like that, as the only developer, with the platform live and in daily use, is a very different problem from migrating a small, well-tested side project.
The problem with skipping versions
The temptation with a legacy codebase is to look for a way to jump straight from 5.4 to 8 and be done with it. In practice that almost always backfires:
- Each major Symfony version removes previously deprecated APIs. Skip a version and you lose the deprecation warnings that would have told you exactly what to fix before it became a hard break.
- Third-party bundles rarely support more than one or two major versions ahead of what they were built for, so a big jump forces several bundle replacements at once instead of one at a time.
- When something breaks after a four-version jump, there is no way to tell which of the four versions introduced the regression.
Strategy: one major version at a time
The approach that actually worked was boring on purpose:
- Upgrade to the next major version only, keeping everything else the same.
- Run the app locally and in staging, fix every deprecation warning Symfony itself reports, and only then move on.
- Run the regression tests that exist, plus a manual pass through the critical flows (sign-ups, bookings, payments) before each deploy.
- Deploy that single version bump to production, watch logs and error tracking for a few days, then start the next version.
It is slower than a single big jump, but every step is small enough to understand and revert if something goes wrong, and the platform never had to go down for it.
Lessons learned
- Deprecation warnings are a to-do list, not noise. Treating every warning as something to fix before the next upgrade turned an otherwise risky migration into a predictable checklist.
- Legacy code with thin test coverage needs a human safety net. Where automated tests did not exist yet, a short manual checklist of the platform's critical paths caught issues that would otherwise have reached real gyms.
- Doing it as a single developer is a scheduling problem as much as a technical one. Spacing the four upgrades out over time, instead of trying to batch them, kept each change reviewable and the platform stable throughout.
The result: MasMusculo/MMfit now runs on Symfony 8, migrated without downtime and without feature regressions reported by the gyms using it daily.