Updating apk add definitions in Dockerfiles

Featured image for sharing metadata for article

As I've written about before, I'm a big fan of Renovate.

Recently at Elastic, we've been using the excellent Chainguard Images to provide zero CVE base image containers, and using Renovate to update those base images as Chainguard ship new updates.

Once we'd set up Renovate to update our base image updates, my colleague Mattias did some great work to dig into how to update the APK package versions used, too.

This took advantage of the renovate-apk-indexer project, allowing us to wire in a Custom Datasource to fetch information about which updates are available for a given APK package.

As a means to produce a reusable custom manager in our internal Renovate shared configuration presets, so all teams using Chainguard base images get the benefits, I've been toying with trying to find a reasonable regex that will allow us to manage these packages in our Dockerfiles.

Although this is focussed on Chainguard packages, it should be possible to reuse this for any APK-based package registry, and could be wired into the Repology datasource, if the distro has packages available for Repology to index.

For instance, let's say we have the following Dockerfile:

# ...

RUN apk upgrade --no-cache && \
    apk add --no-cache \
        bash=5.2.37-r2 \
        py3-pip \
        python3 \
        rsyslog=8.2412.0-r1 \
        runit=2.2.0
ENV APP_HOST=0.0.0.0
ENV app_port=5000
ENV SNAPP_PORT 5000

In the above, we want to capture the dependencies:

  • bash with currentValue=5.2.37-r2
  • py3-pip with currentValue=latest
  • python3 with currentValue=latest
  • rsyslog with currentValue=8.2412.0-r1
  • runit with currentValue=2.2.0

We can extract these with a fairly naive regex:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "customManagers": [
    {
      "description": "...",
      "customType": "regex",
      "fileMatch": [
        "^Dockerfile$"
      ],
      "matchStrings": [
        "(?<depName>\\S+)=(?<currentValue>\\S+)"
      ],
      "datasourceTemplate": "custom.chainguard-apk"
    }
  ],
  "packageRules": [
    {
      "matchDatasources": [
        "custom.chainguard-apk"
      ],
      "versioning": "regex:^(?<major>\\d+)(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))?(-r(?<build>\\d+))?(-(?<compatibility>\\w+))?$"
    }
  ]
}

This works as a first pass, as it does correctly pick up versioned packages, such as bash with currentValue=5.2.37-r2, but it doesn't pick up an unversioned package py3-pip, as well as incorrectly picking up app_port with currentValue=5000:

{
  "deps": [
    {
      "depName": "bash",
      "currentValue": "5.2.37-r2"
    },
    {
      "depName": "rsyslog",
      "currentValue": "8.2412.0-r1"
    },
    {
      "depName": "runit",
      "currentValue": "2.2.0"
    },
    {
      "depName": "APP_HOST",
      "currentValue": "0.0.0.0"
    },
    {
      "depName": "app_port",
      "currentValue": "5000"
    }
  ]
}

Fortunately, I found this configuration from Charles Korn, and adapted it for the Dockerfile format above:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "customManagers": [
    {
      "description": "...",
      "customType": "regex",
      "fileMatch": [
        "^Dockerfile$"
      ],
      "matchStrings": [
        "(?:RUN apk add --no-cache\\s+|\\\\\\s+)(?<depName>[a-zA-Z0-9-]+)(=(?<currentValue>[a-zA-Z0-9-._]+))?"
      ],
      "datasourceTemplate": "custom.chainguard-apk",
      "currentValueTemplate": "{{#if currentValue }}{{{currentValue}}}{{else}}latest{{/if}}"
    }
  ],
  "packageRules": [
    {
      "matchDatasources": [
        "custom.chainguard-apk"
      ],
      "versioning": "regex:^(?<major>\\d+)(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))?(-r(?<build>\\d+))?(-(?<compatibility>\\w+))?$"
    }
  ]
}

Then, when we extract the dependencies with Renovate, we see all the versions we'd expect:

{
  "deps": [
    {
      "depName": "apk",
      "currentValue": "latest"
    },
    {
      "depName": "bash",
      "currentValue": "5.2.37-r2"
    },
    {
      "depName": "py3-pip",
      "currentValue": "latest"
    },
    {
      "depName": "python3",
      "currentValue": "latest"
    },
    {
      "depName": "rsyslog",
      "currentValue": "8.2412.0-r1"
    },
    {
      "depName": "runit",
      "currentValue": "2.2.0"
    }
  ]
}

Notice that this does, unfortunately, pick up an erroneous apk, but that's something I'll try and remove in the future.

This ends up solving what we need, and wiring it into a running instance of the renovate-apk-indexer means Renovate can now correctly update our APK packages if they're pinned πŸš€

(Aside: to try and write this regex, I ended up asking qwen2.5-coder:32b, but after a few hours on-and-off, had no luck, so I decided to give ChatGPT, Claude and Copilot a go to see if they could help, but they all failed gloriously).

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #renovate.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.