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” 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:
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:
- Downloading NVM with
git
.
# 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
...
- Downloading minimum
init
binary withcurl
.
# 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 documentation for even more sources.
Renovate do not understand our comments yet, so let teach it 😉
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.