Almost every Ember upgrade we are called into is expensive for the same reason. The app is not hard to upgrade; it is simply three or four years in arrears, so a single hop carries a Node bump, a build change, two abandoned addons, a hundred deprecations, and a test suite that has not been green on a fresh install since 2022. The work is real, but most of it is interest on deferred maintenance rather than the upgrade itself.
The cheap fix is unglamorous: make dependency updates arrive continuously, in small reviewable batches, and make CI tell you the truth about them. Teams that do this stop having upgrade projects. They have upgrade Tuesdays.
Here is how we set it up, in the order we set it up.
Step 0: get CI honest first
Do not automate updates into a pipeline you do not trust. Before any bot is turned on, CI needs three properties:
- A clean install from the lockfile. No cached
node_modulesshortcuts, no post-hoc manual steps. If a fresh clone cannot install and build, every bot PR will fail for reasons unrelated to the bot. - A pinned Node version, declared in one place. Put the version in
.nvmrcorvoltaconfig and read it in CI from there. Node is the dependency that silently blocks Ember upgrades most often, and disagreement between laptops and CI is how that stays invisible. - A test suite whose failures mean something. Flaky tests are fatal to this whole approach: if a third of bot PRs fail randomly, the team learns to merge them unread, which is worse than not automating at all. Quarantine the known-flaky tests into a separate job, get the main job reliable, and fix the quarantine on a schedule.
That last point is usually where the real work is. It is worth it before anything else, because a trustworthy suite is what converts "an upgrade PR" into "a five-minute review."
Step 1: Renovate, configured for Ember's shape
Renovate or Dependabot both work; we reach for Renovate because its grouping and scheduling controls fit an Ember dependency graph better. The default configuration, though, will bury you — a mature app has hundreds of transitive dev dependencies, and one PR per package is noise, not signal.
The configuration principles that matter:
Group by ecosystem, not by package. One PR for the ember-source / ember-cli / ember-data core, one for the Embroider and build packages, one for linting, one for test tooling, one for type packages. Related packages move together in practice, so splitting them produces PRs that cannot pass CI alone.
Separate patch and minor from major. Patch and minor updates within the same major line should be automerged when CI is green — lint rules, type packages, test helpers. Majors always get human review. This is the rule that changes the team's life: the volume of updates is mostly patches, and a human should not be reading them.
Pin Ember itself and upgrade it deliberately. Do not let a bot walk ember-source across LTS boundaries. Keep it on a schedule you control and do that hop as a considered piece of work, using the playbook rather than a merge button.
Set a schedule and a concurrency limit. Weekly, off-hours, with a cap of five or six open PRs. Continuous does not mean constant; a predictable weekly batch is what a team can actually absorb.
Add a dependency dashboard. Renovate's dashboard issue is the single most useful artifact here: it lists every update the bot is holding back and why. That list is your real dependency risk register, and it is free.
Step 2: an ember-try matrix that answers the next question
Renovate tells you about today. ember-try tells you about the version you are about to move to. In a mature app we keep three or four scenarios:
- the current lockfile, which is what ships;
- the current LTS, if the app is not already on it;
- the next LTS or the beta channel, allowed to fail;
- one scenario for whatever migration is in flight — Embroider enabled, or the Vite build, or a candidate addon replacement.
Run the shipping scenario on every PR and the rest nightly. The allowed-to-fail future scenario is the important one: it converts "the next upgrade is an unknown" into a burndown list you can see months ahead. When a next-LTS scenario has been green for weeks, the hop is a formality. When it goes red, you find out on the day the breakage lands upstream, with a small diff to read, instead of two years later with a thousand-line one.
Step 3: put deprecations on a budget
Deprecation warnings are the currency of Ember upgrades — clearing them is most of the work of the next major. Treat the count as a tracked number rather than console noise.
Use ember-cli-deprecation-workflow to snapshot the current set and silence it, then make the rule that new deprecation IDs fail the build. Existing ones get worked down deliberately; the snapshot file becomes a visible, shrinking list, and the ratchet stops the count from growing while you do. Codemods handle a surprising share of the backlog mechanically. What remains is genuine design work, and it is better to discover that over a year of small PRs than in the week you planned to upgrade.
Step 4: make the numbers visible
Four metrics, reported somewhere a manager sees them:
- Days behind the current Ember LTS.
- Count of direct dependencies with no release in eighteen months.
- Open deprecation IDs in the workflow snapshot.
- Median time from bot PR opened to merged.
None of these are vanity numbers. The first two predict the cost of the next upgrade. The third predicts the cost of the next major. The fourth tells you whether the automation is working or whether bot PRs are quietly rotting, which is the usual failure mode — a repo with forty stale Renovate PRs is in a worse position than one with none, because the backlog now hides the signal.
What this does not fix
Be clear-eyed about the limits. Automation does not replace an abandoned addon for you; it only tells you, promptly, that nobody has published it in two years. It does not make a major version transition free. It will not survive a team that has no time allocated to it — an hour a week, owned by a named person, is the minimum viable commitment, and without that the bot becomes another source of ignored notifications.
And if the app is already far in arrears, do not start here. Start with one deliberate catch-up engagement to reach a current LTS and a green suite, and turn the bot on at the end of it. Automation is how you keep a healthy app healthy. It is not how you rescue one.
The payoff, when it is in place, is that upgrades stop being projects. You are a few weeks behind current at all times, you know what is coming, and the question "can we move to the new LTS this quarter" has an answer you can give in a meeting without a two-week investigation first.