Maintenance Is a Habit, Not a Project

Slaven Karamatić17 May 2026
Weekly report dashboard mockup showing dependency findings

Continuous Maintenance for Rails Applications

Software maintenance is one of the most consistently under-invested areas in product engineering, and as AI tools accelerate development, the surface area that needs maintaining is growing just as fast.

It has no natural advocate, and done well it produces nothing visible to advocate for. Features have product owners. Incidents have upset customers. Maintenance has neither, and without a deliberate process it gets forgotten or treated as discretionary, which amounts to the same thing.

Teams that avoid this outcome typically have a process that turns maintenance into a supplemental activity rather than something a person has to go out of their way to do. Leaving a project in a better state than you found it is significantly cheaper than investigating and fixing what's broken later.

By maintenance I mean future risk reduction: work that lowers the frequency of reactive emergencies and the effort spent on audits and triage. The cost of staying current is low. The cost of catching up is not.

Where to begin

No part of this requires a complete overhaul before it produces value. Each practice is independently deployable and immediately useful.

The minimum viable starting point is a weekly script: run bundler-audit and brakeman, post the output somewhere visible, and establish the habit of reviewing bundle outdated against release dates before merging any updates. Half a day of setup and a small recurring discipline. From there the report can be extended as the team's needs become clearer.

The pattern is incremental by design. A codebase maintained in small, continuous steps does not accumulate the kind of debt that demands intervention. All of this assumes a test suite you trust enough to merge against; if you don't have one, that's the maintenance work to start with.

Monitoring

The precondition for proactive maintenance is continuous visibility. A team cannot act on information it doesn't have, and periodic checks that depend on someone remembering to run them are not infrastructure.

Security and dependency checks belong in a dedicated weekly report, generated on a schedule and delivered wherever the team already looks: a dashboard, a Slack channel, an email digest. What matters is that it gives developers enough confidence to act on the information without escalating, deferring, or working around the gaps.

For a typical Rails application, two tools form the core:

bundler-audit cross-references locked gem versions against the Ruby Advisory Database, surfacing known CVEs in the dependency tree.
brakeman performs static analysis of application code, flagging common vulnerability patterns including SQL injection surfaces, unsafe redirects, and mass assignment risks.

The report can be as simple as a cron job that runs both tools and posts output to a Slack channel, or as structured as a small internal dashboard that tracks findings over time, highlights new items week-over-week, and records when each was acknowledged and resolved. The right level of complexity depends on the team's size and tolerance for tooling overhead. What matters is that the output is visible, frequent, and actually reviewed.

Dependency update policy

Supply chain attacks via malicious package updates have always been a threat and are becoming increasingly common. A freshly updated gem that passes a test suite is still a compromised gem. The recommended policy is to merge dependency updates only once the released version is at least a week old, since malicious releases rarely survive a week without community detection. The sole exception is a fix for a confirmed, actively exploited CVE, which warrants immediate patching regardless of release age.

Running bundle outdated as part of the weekly report and reviewing release dates before merging is sufficient. For teams that prefer automated PR creation, Dependabot can handle that layer.

End of life

Ruby and Rails publish maintenance schedules well in advance. Hosting platforms like Heroku publish supported stack timelines. None of this should ever surprise a team, these are scheduled events. Planning around them turns potential incidents into routine work.

Effective upgrade sprints separate the layers: runtime, framework-blocking dependencies, framework, and everything else, each as its own pass with the test suite run and behaviour verified between them. Interleaving the layers makes it difficult to attribute failures and compounds the work needed to resolve them.

Knowledge distribution

Upgrades go faster when the team still remembers why things are the way they are. A team that loses the context behind its own decisions pays the catch-up cost later, when a routine upgrade turns into archaeology and someone has to reconstruct why a gem was pinned three years ago. Non-obvious decisions should be written down as they're made, wherever the team already reads: a PR comment, a .md file in the repo, a page in the internal wiki. The default is recording context as it's made, so the team a year from now isn't paying to recover what this one already knew.

Closing

None of this is novel and none of it is technically difficult. The hard part is making it routine. Maintenance work only feels expensive when it has been deferred long enough to become a project. When done continuously, it disappears into the rhythm of normal development.

The teams that handle this well aren't the ones with the strictest policies or the most elaborate tooling. They're the ones who process without ceremony: staying current is the default, and there's just enough scaffolding to keep it that way. No reviews to schedule, no initiatives to launch, no debt to pay down. Just the work, done as it appears.