I’ve been writing recently about best practices for patching and deprecating Docker images , but today I want to show how to automate a huge part of this process.

You might already hear about Dependabot1, it’s a Github’s way to notify developers about security vulnerabilities in their projects. Renovate2 is similar tool3, but doesn’t require Github. For my professional work I use Bitbucket, so Renovate feels more universal as can be used anywhere.

What’s the main problem here?

When we write Dockerfiles, we might install system packages (this is easy to update), but sometimes we have to fetch something from Github or Maven. Those binaries might not have a specific repository and updating them is a manual work. Variety of tools and ways of doing it, makes the whole thing a complex task.

That’s where Renovate steps in. It provides a variety of “managers”external link which can automatically determine package versions from known sources (docker, maven, npm, pypi and many more). Those can automatically make patching easier. There’s also special manager, called regex which allows to make impossible a possible, by finding dependencies for custom links. Let me show you how.

How Renovate Bot can help us?

Without much configuration, Renovate can automatically detect changes in Dockerfiles and propose updates of parent image versions to their latest. That’s not much and if you “pin” parent images loosely:

Base image pinning example
FROM alpine:3
...

then it’s not really useful.

It’s getting harder when you want to keep up to date tools downloaded from different sources like with curl or git. For that, we have to prepare a syntax a little bit. Take a look at two examples:

  1. Downloading NVM with git.
Git fetching example
# renovate: datasource=github-releases depName=nvm-sh/nvm
ENV NVM_VERSION v0.39.3
RUN apt-get update && \
    apt-get install -y git && \
    git clone \
        --depth 1 \
        --branch $NVM_VERSION \
        https://github.com/nvm-sh/nvm.git
...

  1. Downloading minimum init binary with curl.
Curl fetching example
# renovate: datasource=github-releases depName=krallin/tini
ARG TINI_VERSION=v0.18.0
RUN curl -fsSLo /usr/local/sbin/tini https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-amd64 && \
    curl -fsSL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-amd64.sha256sum | sha256sum -c && \
    chmod +x /usr/local/sbin/tini && \
    tini -v
...

Important part here is to use ENV or ARG and expose the version we need in single place. We use it to refer to the app version when installing.

Another important thing are # renovate comments. They provide metadata, pointing what kind of datasource 4 and dependency name to use, for example:

  • maven -> # renovate: datasource=maven depName=com.google.code.gson:gson
  • github-releases -> # renovate: datasource=github-releases depName=nvm-sh/nvm
  • docker -> # renovate: datasource=docker depName=tomcat
  • check Renovate’s documentationexternal link for even more sources.

Renovate do not understand our comments yet, so let teach it 😉

Renovate config.js example
module.exports = {
  // check for more here: https://docs.renovatebot.com/modules/platform/
  platform: "github",
  gitAuthor: "renovate <renovate@example.com>",
  forkProcessing: "enabled",

  // renovate usually requires a branch or a config file in the repo,
  // I run it from separate project, to scan multiple Docker images repositories
  requireConfig: "optional",
  onboarding: false,

  // to match those repositories, we have to discover them
  autodiscover: true,
  autodiscoverFilter: [
    "tgagor/docker-*"
  ],

  // we will only use regex module in the example
  // feel free to add more as you need: https://docs.renovatebot.com/modules/manager/
  enabledManagers: ["custom.regex"],

  // we're adding custom regex matcher
  // more examples here: https://docs.renovatebot.com/modules/manager/regex/
  customManagers: [
    {
      customType: "regex",
      fileMatch: ["/Dockerfile$"],
      matchStrings: [
        "#\\s*renovate:\\s*datasource=(?<datasource>.*?)\\s+depName=(?<depName>.*?)(\\s+versioning=(?<versioning>.*?))?\\s*(ENV|ARG)\\s+[A-Z0-9_]+_VERSION[= ]?[\"']?(?<currentValue>.*?)[\"']?\\s",
      ],
      // datasourceTemplate: "{{{datasource}}}",
      // depNameTemplate: "{{{depName}}}",
      // versioningTemplate: "{{#if versioning}}{{{versioning}}}{{else}}semver-coerced{{/if}}"
    },
  ],

  // here we can add per package exceptions
  packageRules: [
    // I keep separate image per major version of Node
    // so I wan't to disable propositions for major upgrades
    {
      matchDatasources: ["node-version"],
      matchUpdateTypes: ["major"],
      enabled: false
    },
    // similar for Python, I want to be informed only about patch
    // level updates
    {
      matchDatasources: ["docker"],
      matchUpdateTypes: ["major", "minor"],
      matchPackagePatterns: ["python"],
      enabled: false
    },
    // let enforce to use our maven repo cache
    // useful if you use Nexus or Artifactory
    {
      matchDatasources: ["maven"],
      registryUrls: ["https://use.custom.maven.repo"],
    },
  ],

  // Git-related customizations
  group: {
    commitMessageTopic: "feat(renovate): upgrading dependencies",
  },
  branchPrefix: "renovate/",
};

The most important part in the config is the custom regex matcher in the customManagers section. It extracts metadata from our comments, and use them to query defined datasources for new version, so Renovate could propose us updated. If we have test images deeply, we can even configure it to auto merge changes if build is passing.

We can also use packageRules to define some custom rules on per package basis or group changes of specific type to not be flooded by PRs.

I’m really fascinated by this tool. It makes boring and annoying task so easy. I can add it to the cron, run monthly or weekly and never by late with the updates again.