← Back to articles

Keep your tech dependencies fresh

How to maintain your software dependencies and when to upgrade them

Most software projects today depend heavily on a variety of external dependencies to handle all kinds of high-level tasks 🤖 (e.g., API clients to connect to network services, development frameworks, and algorithms you’d rather not implement yourself).

These dependencies are typically managed by a package manager and declared in a centralized file (for example, Cargo.toml in Rust or pyproject.toml in Python when using the uv package manager). For each package, you can specify version constraints, like sqlalchemy<2.0, if your software relies on an older API version that’s been deprecated in SQLAlchemy 2.0.

In any production-grade software, the package manager also maintains a lock file 🔒. This file, which can be generated or updated as needed, contains the exact version of every package you use, as well as the dependencies of those packages. When updating the lock file, the package manager tries to use the latest compatible versions of each dependency, while respecting both your specified constraints and those of other dependencies.

Why is this important?

You usually want your program to build and function consistently for a given commit in your version control system. Imagine the following scenario:

Without a lock file, your package manager might grab this new buggy version when you redeploy, breaking your application! The lock file prevents this by pinning dependencies to precise versions, such as foobar==2.13.0, ensuring no unexpected upgrades occur.

Lock File Management

I encourage you to declare your dependencies with the least restrictive constraints possible. Don’t set an upper range without a valid reason. Remember, your lock file is there to protect you.

When it’s time to update your locked dependencies, you’ll test your application with these shiny new versions, and ideally, your CI pipeline will detect most issues. If you find an incompatibility you can’t fix immediately, that’s the time to introduce a version constraint.

Risks of Not Updating Your Locked Dependencies

You must have a process to regularly update your dependencies and ensure your codebase supports the latest versions. Ignoring this can lead to significant risks:

How to Keep Them Fresh

There are many strategies, but here’s the one I recommend:

First, decide on a recurring schedule for updating dependencies. This could be weekly, monthly, or after each new release of your application.

Then, when the time comes:

What About Dependabot/Renovabot?

Tools like Dependabot or Renovabot are excellent for monitoring security issues in your packages and quickly merging a version bump for affected packages.

However, they’re less practical for managing day-to-day dependency upgrades.

A typical tech project often has many dependencies, and I find it more practical to tackle a batch of upgrades at a scheduled time. Too often, automated upgrade MRs pile up without anyone addressing them. Additionally, I’m not entirely comfortable enabling auto-merge for these tools.