Updating apk add
definitions in Dockerfiles

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 Dockerfile
s.
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
withcurrentValue=5.2.37-r2
py3-pip
withcurrentValue=latest
python3
withcurrentValue=latest
rsyslog
withcurrentValue=8.2412.0-r1
runit
withcurrentValue=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).