Small recap

In my earlier post, I already mention Renovate few times:

But a small recap about, what is Renovate. Renovate is an automated dependency update tool. It helps to update dependencies in your code without needing to do it manually. When Renovate runs on your repo, it looks for references to dependencies (both public and private) and, if there are newer versions available, Renovate can create pull requests to update your versions automatically.

What is the plan for now?

I want to expand that things that I can monitor with Renovate. It is very easy, out of the box solution, to monitor Containerfile or Dockerfile. But what about packages within it? To see what is my point here, let’s see a Containerfile implementation:

FROM docker.io/library/debian:bookworm-slim@sha256:b1211f6d19afd012477bd34fdcabb6b663d680e0f4b0537da6e6b0fd057a3ec3

RUN apt-get update \
    && apt-get -y install \
    git \
    wget \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /app
WORKDIR /app
COPY ./build.sh .

RUN wget -q https://github.com/gohugoio/hugo/releases/download/v0.147.0/hugo_extended_0.147.0_Linux-64bit.tar.gz \
    && mkdir hugo_pack \
    && tar -xvf hugo_extended_0.147.0_Linux-64bit.tar.gz -C hugo_pack/ \
    && mkdir bin \
    && mv hugo_pack/hugo bin/hugo \
    && rm -rf hugo pack hugo_extended_0.147.0_Linux-64bit.tar.gz \
    && chmod +x ./build.sh

CMD ["./build.sh"]

So, what is the problem here? Container version is pinned, so that should be fine, so this is a good Containerfile, right? Unfortunately, not enough good (at lest for me). Why? The installed packages are not tracked, like git and wget from Debian repository and hugo from Github release.

So the plan is to make it possible to monitor those with Renovate!

Modify Containerfile

First, I modify the Containerfile. I pin the version for the apt packages. But for practical reasons, I store the version in variables, because it will be easier make regex for that.

Debian package version can be fetched with apt search command. Example for wget:

$ apt search wget
wget/stable,stable,now 1.21.3-1+deb12u1 amd64 [installed]
  retrieves files from the web

The version number for wget is 1.21.3-1+deb12u1. For Hugo, the version is visible in the Github release page: Hugo releases. When I write this article, it is 0.147.0.

After a small modification, this is my Containerfile.

FROM docker.io/library/debian:bookworm-slim@sha256:b1211f6d19afd012477bd34fdcabb6b663d680e0f4b0537da6e6b0fd057a3ec3

ARG GIT_DEB_VERSION=1:2.39.5-0+deb12u2 # suite=bookworm depName=git
ARG WGET_DEB_VERSION=1.21.3-1+deb12u1 # suite=bookworm depName=wget
ARG HUGO_GITHUB_VERSION=0.147.0 # depName=gohugoio/hugo

RUN apt-get update \
    && apt-get -y install \
    git=${GIT_DEB_VERSION} \
    wget=${WGET_DEB_VERSION} \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /app
WORKDIR /app
COPY ./build.sh .

RUN wget -q https://github.com/gohugoio/hugo/releases/download/v0.147.0/hugo_extended_${HUGO_GITHUB_VERSION}_Linux-64bit.tar.gz \
    && mkdir hugo_pack \
    && tar -xvf hugo_extended_${HUGO_GITHUB_VERSION}_Linux-64bit.tar.gz -C hugo_pack/ \
    && mkdir bin \
    && mv hugo_pack/hugo bin/hugo \
    && rm -rf hugo pack hugo_extended_${HUGO_GITHUB_VERSION}_Linux-64bit.tar.gz \
    && chmod +x ./build.sh

CMD ["./build.sh"]

Now we have pinned versions, that’s cool. But unfortunately, Renovate does not support it out of the box.

Write regex to capture the lines

Basically, I have to write two kind of regex:

  • One to catch the Debian version
  • Another one to catch Github release version

I have put some comment after ARG statement, that helps me to give parameters to Renovate. Regex for Debian package:

ARG\s+(?<depName>[A-Z0-9_]+)_DEB_VERSION=(?<currentValue>[^\s#]+)\s+#\s+suite=(?<suite>[^\s]+)\s+depName=(?<packageName>[^\s]+)

Let’s break it what it does mean:

  • ARG\s: Line start with “ARG” and followed by a space.
  • (?<depName>[A-Z0-9_]+): Capture group for depName. It can contains upper case characters, numbers and ‘_’ symbol.
  • _DEB_VERSION=: Followed by this static string.
  • (?<currentValue>[^\s#]+): Capture group for currentValue. Matches with characters that are not whitespace or ‘#’ symbol.
  • \s+#\s+suite=: Followed by a space, then ‘#’ symbol, space again, then ‘suite=’
  • (?<suite>[^\s]+): Capture the suit. This tell which Debian repository should be checked.
  • \s+depName=: Followed by one or more space and ‘depName=’.
  • (?<packageName>[^\s]+): Capture packageName.

The regex for Github release looks very similar.

"ARG\s+(?<depName>[A-Z0-9_]+)_GITHUB_VERSION=(?<currentValue>[^\s#]+)\s+#\s+depName=(?<packageName>[^\s]+)"

The final renovate.json can be seen in the next code snippet.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "customManagers": [
    {
      "customType": "regex",
      "managerFilePatterns": ["/^Containerfile$/"],
      "matchStrings": [
        "ARG\\s+(?<depName>[A-Z0-9_]+)_DEB_VERSION=(?<currentValue>[^\\s#]+)\\s+#\\s+suite=(?<suite>[^\\s]+)\\s+depName=(?<packageName>[^\\s]+)"
      ],
      "datasourceTemplate": "deb"
    },
    {
      "customType": "regex",
      "managerFilePatterns": ["/^Containerfile$/"],
      "matchStrings": [
        "ARG\\s+(?<depName>[A-Z0-9_]+)_GITHUB_VERSION=(?<currentValue>[^\\s#]+)\\s+#\\s+depName=(?<packageName>[^\\s]+)"
      ],
      "datasourceTemplate": "github-releases",
      "autoReplaceStringTemplate": "ARG {{{depName}}}_GITHUB_VERSION={{{newValue}}} # depName={{{packageName}}}"
    }
  ]
}

💡 Tip

In renovate.json the “\s” must be escaped with “\\s”.

Test Renovate configuration locally

I want to be sure that this configuration works as intended before I would merge it with main. To do it, Renovate must be installed locally. This is explained here. Then it has to be run locally, by the following command.

But before run it, I reduced the version numbers in Containerfile. So I must get new version update in the log!

LOG_LEVEL=debug npx renovate --platform=local --onboarding=false

The output is verbose, I don’t paste full here, just the important part.

It can be seen, that it has found one dockerfile and 3 regex matches. First sign, it looks good.

 INFO: Dependency extraction complete (repository=local)
       "stats": {
         "managers": {
           "dockerfile": {"fileCount": 1, "depCount": 1},
           "regex": {"fileCount": 2, "depCount": 3}
         },
         "total": {"fileCount": 3, "depCount": 4}
       }

The next part validates that my configuration is okay, because it shows upgrade for Hugo and Git packages, that I set to previous version before run renovate.

DEBUG: packageFiles with updates (repository=local)
       "config": {
         "dockerfile": [
           {
             "deps": [
               {
                 "depName": "docker.io/library/debian",
                 "packageName": "docker.io/library/debian",
                 "currentValue": "bookworm-slim",
                 "currentDigest": "sha256:b1211f6d19afd012477bd34fdcabb6b663d680e0f4b0537da6e6b0fd057a3ec3",
                 "replaceString": "docker.io/library/debian:bookworm-slim@sha256:b1211f6d19afd012477bd34fdcabb6b663d680e0f4b0537da6e6b0fd057a3ec3",
                 "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
                 "datasource": "docker",
                 "depType": "final",
                 "updates": [],
                 "versioning": "docker",
                 "warnings": []
               }
             ],
             "packageFile": "Containerfile"
           }
         ],
         "regex": [
           {
             "deps": [
               {
                 "depName": "git",
                 "packageName": "git",
                 "currentValue": "1:2.39.5-0+deb12u1",
                 "datasource": "deb",
                 "replaceString": "ARG GIT_DEB_VERSION=1:2.39.5-0+deb12u1 # suite=bookworm depName=git",
                 "updates": [  <==== HERE WE HAVE THE DEBIAN PACKAGE UPDATE
                   {
                     "bucket": "non-major",
                     "newVersion": "1:2.39.5-0+deb12u2",
                     "newValue": "1:2.39.5-0+deb12u2",
                     "newMajor": 2,
                     "newMinor": 39,
                     "newPatch": 5,
                     "updateType": "patch",
                     "branchName": "renovate/git-2.x"
                   }
                 ],
                 "versioning": "deb",
                 "warnings": [],
                 "registryUrl": "https://deb.debian.org/debian?suite=stable&components=main,contrib,non-free&binaryArch=amd64",
                 "homepage": "https://git-scm.com/",
                 "currentVersion": "1:2.39.5-0+deb12u1",
                 "isSingleVersion": true,
                 "fixedVersion": "1:2.39.5-0+deb12u1"
               },
               {
                 "depName": "wget",
                 "packageName": "wget",
                 "currentValue": "1.21.3-1+deb12u1",
                 "datasource": "deb",
                 "replaceString": "ARG WGET_DEB_VERSION=1.21.3-1+deb12u1 # suite=bookworm depName=wget",
                 "updates": [],
                 "versioning": "deb",
                 "warnings": [],
                 "registryUrl": "https://deb.debian.org/debian?suite=stable&components=main,contrib,non-free&binaryArch=amd64",
                 "homepage": "https://www.gnu.org/software/wget/",
                 "currentVersion": "1.21.3-1+deb12u1",
                 "fixedVersion": "1.21.3-1+deb12u1"
               }
             ],
             "matchStrings": [
               "ARG\\s+(?<depName>[A-Z0-9_]+)_DEB_VERSION=(?<currentValue>[^\\s#]+)\\s+#\\s+suite=(?<suite>[^\\s]+)\\s+depName=(?<packageName>[^\\s]+)"
             ],
             "depNameTemplate": "{{ matches.packageName }}",
             "datasourceTemplate": "deb",
             "packageFile": "Containerfile"
           },
           {
             "deps": [
               {
                 "depName": "gohugoio/hugo",
                 "packageName": "gohugoio/hugo",
                 "currentValue": "0.146.7",
                 "datasource": "github-releases",
                 "replaceString": "ARG HUGO_GITHUB_VERSION=0.146.7 # depName=gohugoio/hugo",
                 "updates": [   <==== HERE WE HAVE THE HUGO GITHUB RELEASE UPDATE
                   {
                     "bucket": "non-major",
                     "newVersion": "v0.147.0",
                     "newValue": "0.147.0",
                     "releaseTimestamp": "2025-04-25T17:17:15.000Z",
                     "newVersionAgeInDays": 0,
                     "newMajor": 0,
                     "newMinor": 147,
                     "newPatch": 0,
                     "updateType": "minor",
                     "libYears": 0.008160641806189752,
                     "branchName": "renovate/gohugoio-hugo-0.x"
                   }
                 ],
                 "versioning": "semver-coerced",
                 "warnings": [],
                 "sourceUrl": "https://github.com/gohugoio/hugo",
                 "registryUrl": "https://github.com",
                 "currentVersion": "v0.146.7",
                 "currentVersionTimestamp": "2025-04-22T17:48:01.000Z",
                 "currentVersionAgeInDays": 3,
                 "isSingleVersion": true,
                 "fixedVersion": "0.146.7"
               }
             ],
             "matchStrings": [
               "ARG\\s+(?<depName>[A-Z0-9_]+)_GITHUB_VERSION=(?<currentValue>[^\\s#]+)\\s+#\\s+depName=(?<packageName>[^\\s]+)"
             ],
             "depNameTemplate": "{{ matches.packageName }}",
             "datasourceTemplate": "github-releases",
             "packageFile": "Containerfile"
           }
         ]
       }

Now that everything looks good, commits can be pushed, then merged with main!

See the result in practice

After merge, I have let run my Renovate container to see what is happening. Dependency dashboard has been open and shows what is expected.

dep_dashboard

Final words

I really start to like Renovate, it has a lot of potential and customization option. It is not a question that I will keep to use it in the future!