Managing Alpine Linux Based Container Images With Renovate
With it's default configuration, Renovate does a great job of managing container
image tag updates. One limitation is that for updates that include an OS in the
tag, for example the Alpine Linux version in python:3.11.3-alpine3.17
,
Renovate only updates Python image tags matching that version, which may limit
updates, and doesn't identify any OS updates. This post details how to configure
Renovate to work around that limitation for Alpine images.
This post assumes you have a working knowledge of Renovate and covers some more advanced topics. You can find more introductory information in the Renovate docs.
Docker versioning overview #
Container image tag versions can be wildly different, sometimes chaotic, but
there are some general conventions. Renovate tries its best to leverage them
with docker
versioning, which is the default for all container image tags.
Renovate treats any SemVer-like initial number as the "version," and it maintains the "version" with the given specificity, so:
node:18
is updated tonode:19
node:18.16
is updated tonode:18.17
node:18.16.0
is updated tonode:18.16.1
Anything after the first hyphen in an image tag is treated as the
"compatibility" indicator. Renovate only identifies updates that maintain this
"compatibility." So node:18.16.0-alpine
is updated to another -alpine
image,
for example node:18.16.1-alpine
.
Complete details are available in the Renovate docs.
Docker versioning and OS versions #
In most cases maintaining image "compatibility" is desired, as you wouldn't want
python:3.11.3-slim-bullseye
updated to python:3.11.4-bullseye
or
python:3.11.4-alpine
since they're completely different base images.
This can also be a challenge, though, in cases where the OS version changes. For
example python:3.11.3-alpine
is updated to python:3.11.4-alpine
, but what if
you're using a specific OS version, like python:3.11.3-alpine3.16
? You might
notice that python
updates stop appearing even though new python
images are
released. In this case the Python v3.11.3 image was the last version released on
Alpine 3.16, so Renovate doesn't recognize any newer image tags that maintain
"compatibility," and there are no further updates.
The other challenge is that images with updated OS versions are not identified
as updates, so you might not know that Alpine 3.17 images were available
starting with Python 3.7.15, or Alpine 3.18 images were available starting with
Python 3.7.16. The same issue exists with other OSs, including Debian and
Ubuntu. If you're using python:3.11.4-bullseye
, Renovate does not identify an
update to python:3.11.4-bookworm
.
Why track OS versions #
Given the previously identified issues, why would someone want to track specific OS versions if it can prevent future updates? As with almost everything in software, different use cases drive different solutions.
As an example from Docker Hub, the following tags all point to the latest
python
image: alpine
, 3-alpine
, 3.11-alpine
, 3.11.4-alpine
,
3.11.4-alpine3.18
. If the uses case is fairly generic, for example a
Python-based linting tool, it may not be overly sensitive to Python or Alpine
changes, so using python:alpine
may be appropriate. For something like the
base image for a containerized application, it's best to be as specific as
possible. This causes Renovate to identify any changes - application or OS - and
make the appropriate update, initiating a CI pipeline. This ensures any change
is tested before being merged into the codebase.
The workaround #
The way to work around the Renovate constraints is to keep the docker
manager
for the docker
versioning, and add a
regex
manager to setup
a separate versioning mechanism for the Alpine changes. The regex
manager
allows a fully customized Renovate configuration to manage dependencies that the
other managers do not cover, and is the only way to have Renovate version the
same dependency multiple ways. It also requires more complex configuration,
which is detailed below.
Thanks to the Renovate team for their help sorting out this solution.
Image tag format #
As noted in the preceding example, there is not strict versioning in container image tags, so investigating tags of interest in tracking revealed the following list:
docker:23.0.6-alpine3.17
docker:23.0.6-dind-alpine3.17
node:18.16.0-alpine3.17
python:3.11.4-alpine3.17
mcr.microsoft.com/powershell:7.3-alpine-3.17
The example below covers these cases, but other image tags may drive Renovate configuration tweaks.
Renovate configuration #
The following section details the configuration of the regex
manager to
version the Alpine OS. For any values that are regular expressions, they're
specified as the JSON string representation of the regular expression, so any
\
must be escaped as \\
.
The fileMatch
property is required and identifies regular expressions to match files to check.
In this case set it to ["\\.gitlab-ci\\.yml$", "Dockerfile"]
, which checks all
GitLab CI files (assuming they end in .gitlab-ci.yml
) and all Dockerfiles
(which matches Dockerfile
, Dockerfile.<value>
, or <value>.Dockerfile
).
This could be updated with any other required files.
The
matchStrings
property is required and identifies regular expressions to match the
dependencies that the regex manager should manage (in this case container
images). To keep it as flexible as possible it has a depName
capture group
with the dependency name, and a currentValue
capture group with the version.
So, for the image and tag that is (?<depName>[\\S]+):(?<currentValue>[\\S]+)
,
using non-whitespace (\S
) to allow any valid characters for both.
To constrain the matchStrings
further, the regular expression is updated with
the keywords indicating a container image. For Dockerfile
s, that's simply
FROM <image_name>
. For .gitlab-ci.yml
files, the following cases are
covered:
image:
name: <image_name>
image: <image_name>
services:
- <image_name>
Note this does not cover all variations of container images in services
(it's
missing at least two), but includes all that I am currently using. See the
GitLab services
docs for
additional details.
To cover these cases preface the previous matchString
with each of them OR'd
together, which results in:
"matchStrings": [
"(?:image:\\s+name:\\s*|image:\\s*|services:\\s+-\\s+|FROM\\s+)(?<depName>[\\S]+):(?<currentValue>[\\S]+)"
]
See here for a full breakdown of this regular expression, with examples.
The
datasourceTemplate
property specifies the data source type, which is docker
.
For versioning image tags, specifically trying to override the default docker
versioning behavior, specify a
versioningTemplate
property telling Renovate how to extract data from the currentValue
(that is,
the image tag). The default behavior is described previously, which is going to
be reversed to track the Alpine versions. Taking the previous python
example,
the tag 3.11.4-alpine3.17
has compatibility
= 3.11.4-alpine
, major
version = 3
, and minor
version = 17
. Using the previous data on different
tags formats gives:
"versioningTemplate": "regex:^(?<compatibility>[\\S]*\\d+\\.\\d+(?:\\.\\d+)?(?:[\\S]*)?-alpine-?)(?<major>\\d+)\\.(?<minor>\\d+)(?:\\.(?<patch>\\d+))?$"
See here for a full breakdown of this regular expression, with examples.
The final Renovate configuration for this regex manager is shown below:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", "docker:enableMajor"],
"regexManagers": [
{
"description": "Manage Alpine OS versions in container image tags",
"fileMatch": ["\\.gitlab-ci\\.yml$", "Dockerfile"],
"matchStrings": [
"(?:image:\\s+name:\\s*|image:\\s*|services:\\s+-\\s+|FROM\\s+)(?<depName>[\\S]+):(?<currentValue>[\\S]+)"
],
"versioningTemplate": "regex:^(?<compatibility>[\\S]*\\d+\\.\\d+(?:\\.\\d+)?(?:[\\S]*)?-alpine-?)(?<major>\\d+)\\.(?<minor>\\d+)(?:\\.(?<patch>\\d+))?$",
"datasourceTemplate": "docker"
}
]
}
The limitations #
In general, the default docker
versioning and regex
versioning work
independently, and should identify updates for either or both, as applicable.
The one known limitation is when both identify a major update, or both identify
a minor/patch update. This can be seen with images like
python:3.11.3-alpine3.17
, which has a minor version update to Python 3.11.4
and also a "minor" version update to Alpine 3.18 (since major/minor are defined
this way in the custom versioning). So, Renovate sees two python-3.x
updates,
and one of them is ignored (in the Renovate logs you'll see the message
"Ignoring upgrade collision"). With this example the python:3.11.4-alpine3.17
update alone is processed, and once it's merged the python:3.11.4-alpine3.18
update is processed.