Skip to main content

Upgrading Dependency Track from v4 to v5

Dependency Track is an open source component analysis platform from OWASP. You upload the SBOM of your application, and Dependency Track keeps track of the components inside it. It checks those components against vulnerability sources like the National Vulnerability Database, GitHub Advisories and OSV. It also lets you define policies and send notifications when something new shows up. In short: it tells you which of your applications are affected when the next vulnerable library hits the news.

Recently Dependency Track got an upgrade and version 5 was released. So, time to upgrade!

However, that turned out not to be as easy as expected. It took us 2 attempts. Our first attempt failed completely, so we took a different route. Here is what we tried and the two things that cost us the most time.

Big shout out to Jef, who looked over my shoulder during the upgrade and helped tackling the issues when we got stuck.

Two approaches in the documentation

The Dependency Track documentation describes two ways to get a new version running.

  • Upgrading running instances: the in-place approach. You run the schema migrations, then replace the API server instances one at a time. This gives you no planned downtime, as long as more than one instance is running and the release notes don't call for a full stop.
  • Migrating from v4 to v5: a separate migration. The v4-migrator tool copies your v4 data into a new v5 database. v4 has to be offline while it runs, and v5 only supports PostgreSQL.

We started with the first one. That turned out to be no success for us.

Attempt 1: the in-place upgrade

We pointed the v5 container at our existing v4 database and let it migrate on startup.

That didn't work. The migration process started, but failed with the following error message:

Caused by: org.flywaydb.core.internal.sqlscript.FlywaySqlScriptException: Failed to execute script V202605111028__add_latest_version_published_at_column_to_package_metadata.sql

No matter what we tried, we couldn't fix this error.

Remark: v5 renames many configuration properties and environment variables, and it no longer accepts the v4 names. If you reuse your v4 configuration in Azure Container Apps, check it against the v5 configuration reference.


Attempt 2: migrate the database separately

Instead of one big step, we split the upgrade in two. First, we migrated the data into a v5 database with v4-migrator, then we started the v5 application on top of it.

Before you start, the documentation lists a few requirements:

  • v4 must be on version 4.14.2 or later, and the v4 API server must be stopped.
  • The target is a dedicated PostgreSQL 14+ database. Don't start the v5 API server against it before the migration has run.
  • The database user you use for the migration should be the same one the v5 API server uses afterwards.

The migration runs in steps: bootstrap applies the v5 schema on the target database, verify checks the target, and run does the extract, transform and load. Afterwards you verify again and cleanup the staging schema.

The migration tool is available in a docker container that we ran locally during the upgrade process.

Two things cost us a lot of time.

Caveat 1: the quotes in the documentation

The documentation shows the JDBC URLs in single quotes:

docker run --rm -t ghcr.io/dependencytrack/v4-migrator:5.0.0 bootstrap \
  --target-url 'jdbc:postgresql://target-host:5432/dtrack' \
  --target-user dtrack \
  --target-pass

That doesn't work for us. It only worked after we removed the quotes:

docker run --rm -t ghcr.io/dependencytrack/v4-migrator:5.0.0 bootstrap \
  --target-url jdbc:postgresql://target-host:5432/dtrack \
  --target-user dtrack \
  --target-pass

The error didn't point at the quotes, so we checked permissions, connectivity, versions (and even the weather) first.

Caveat 2: the pg_trgm extension

Dependency Track v5 needs the pg_trgm extension. The bootstrap step tries to install it, and the preflight check verifies it.

This extension was not installed out-of-the-box. So, the preflight check failed. Because we were using an Azure Database for PostgreSQL Flexible Server: the extension must be allow-listed first through the azure.extensions server parameter:

After that, we could create the extension:

CREATE EXTENSION IF NOT EXISTS pg_trgm;

Starting the v5 application

With the migrated database in place, we could finally deploy the v5 container to Azure Container Apps.

This time it started without problems.

Tip: The migration is deliberately lossy in a few places. Every notification rule is disabled after the migration, and repository and analyzer credentials have to be entered again in the v5 secret manager. Plan some time for this after the cutover.

That's it!

More information

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Cache stampede: when our cache turned against us

While investigating some performance issues, we ran into an ASP.NET Core API that cached a fairly expensive aggregation query for 60 seconds. Under normal load, that was fine: one request rebuilds the cache, everyone else reads from it. Under peak load, dozens of requests would arrive in that same expiry window, all see a cache miss, and all fire the same expensive query in parallel. The database didn't like that. That was the moment when our caching layer stopped helping and started hurting. A burst of requests comes in at the same time, all miss the cache, and all go hammer the database or the downstream API at once. That's a cache stampede . The cache was supposed to protect our backend, and for a few hundred milliseconds it did the opposite. Why this happens IMemoryCache.GetOrCreate (and its async sibling) looks like it protects you, but it doesn't add any locking on its own. Look at the naive version: public async Task<Report> GetReportAsync(string key) ...

VS Code Planning mode

After the introduction of Plan mode in Visual Studio , it now also found its way into VS Code. Planning mode, or as I like to call it 'Hannibal mode', extends GitHub Copilot's Agent Mode capabilities to handle larger, multi-step coding tasks with a structured approach. Instead of jumping straight into code generation, Planning mode creates a detailed execution plan. If you want more details, have a look at my previous post . Putting plan mode into action VS Code takes a different approach compared to Visual Studio when using plan mode. Instead of a configuration setting that you can activate but have limited control over, planning is available as a separate chat mode/agent: I like this approach better than how Visual Studio does it as you have explicit control when plan mode is activated. Instead of immediately diving into execution, the plan agent creates a plan and asks some follow up questions: You can further edit the plan by clicking on ‘Open in Editor’: ...