As part of future changes, we're introducing a means to parse
Dockerfiles for any `apk add` commands, so we can propose updates to any
packages using the APK datasource.
Before we do this, we need to correct a couple of bugs in our
implementation of the APK versioning scheme, so we can handle i.e.
apk add curl=~8.12.1
When the `apk` CLI builds a version constraint, it `OR`s one flag per
operator character[0].
For instance, `~` sets both "fuzzy" and "equal".
These operator characters may be in any order and may repeat, so `~`,
`=~` and `~=` are all the same operator, and `>>` and `>` are the same.
To correct our parsing, we should take this into account, rather than
using known patterns.
When we perform a fuzzy match, this is performed over version _tokens_
not the version itself, which means that:
- `~1.6` matches `1.6`, `1.6.0_pre1`, `1.6.0`, `1.6.5` and `1.6.9_p1`
- `~1.6` does not match `1.60`
- `~8.12.1` matches every `8.12.1-rN` i.e. `8.12.1-r1` and `8.12.1-r100`
While doing this, we also fix related issues:
- `~` previously treated this as an npm-style tilde ("same major and
minor, and greater than or equal"),
- admitted fuzzy versions that `apk` rejects - `~2.39.0-r0` should not have
matched `2.39.1-r0`
- rejected fuzzy versions that `apk` accepts - `~2.39.0` should have
matched `2.39.0-r1`
- `=~` and `~=` were not allowed
- `isSingleVersion('=~1.2.3')` was true, so a range looked like a pin
- `isLessThanRange` compared against the raw range, so `_parse` failed on
the operator and it always returned false
- `minSatisfyingVersion` was inherited, so it only ever found an exact
match
- `getNewValue` dropped the operator entirely, rewriting `~8.12.1` to a
bare version
We also set `supportsRanges`, which we'd forgotten to set despite `~`
and `>=` being ranges, and note that we support `pin`, `replace` and
`bump` strategies.
When using `getNewValue`, we now keep the precision the constraint was
written with, as otherwise we'd quietly narrow what the user asked for:
- `~8.12.1` becomes `~8.13.0` (previously `~8.13.0-r0`)
- `~8.12` becomes `~8.13`
- `~8.12.1-r0` becomes `~8.13.0-r2` (with a new revision number)
For `bump`, we raise a lower bound which includes its boundary, as with
`npm` and `pep440`, so:
- `>=8.12.1` becomes `>=8.13.0`
- `>~8.12` becomes `>~8.13`
Otherwise we return `null`, logging which constraint we couldn't handle
in the `bump` case, as `npm` does:
- `>` on its own can't be bumped, as `>8.13.0` would exclude the version
we bump to
- `<` and `<=` are upper bounds, so there's no lower bound to raise
- for `replace`, there's no single obvious new bound for `<`, `<=`, `>`,
`>=`, `>~` or `<~`, and a bound which already admits the new version
doesn't need one
- a prefix constraint whose version doesn't parse, i.e. `~foo`, which
would otherwise throw
`><` constrains a package to an identity hash rather than to a version,
so it's rejected outright.
Additionally, `isLessThanRange` and `matches` weren't agreeing on how to
resolve a fuzzy constraint.
For instance, `_compare` ranks `2.39.0_git20240101` below `2.39.0`, but
`~2.39.0` matches it, this would lead to a rollback of sorts.
Instead, we can ignore the `_git` and `_p` suffixes, which are common in
Alpine and Wolfi packages.
[0]: https://gitlab.alpinelinux.org/alpine/apk-tools/-/blob/44dcdfc255c26a4e19a4a27f66a54169cce5ca72/src/version.c
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
The APK datasource is designed to work with Alpine Linux package repositories. It can fetch package information from APK repositories and provide version updates for Alpine Linux packages.
Alpine Linux uses the APK package manager, and packages are distributed through repositories.
Each repository contains an APKINDEX.tar.gz file that contains metadata about all available packages.
Files are typically located in this structure:
https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
Set URL when using an APK repository
To use an APK repository with the datasource, you must set a properly formatted URL with specific query parameters as registryUrl:
arch: Architecture of the binary packages (e.g.x86_64,aarch64,armv7).branch: Alpine branch, either a rolling alias likelatest-stableoredge, or a fixed release likev3.19. Omit this parameter for repositories which do not have a branch in their path.components: Comma-separated list of repository components (e.g.main,community,testing). Omit this parameter for repositories which serve their index directly below the repository root.
!!! note
Renovate does not fetch the registryUrl as-is.
It combines the base URL with these parameters to build the actual index URLs, for example <base>/<branch>/<component>/<arch>/APKINDEX.tar.gz.
Only the parameters above are accepted, so that a typo fails loudly instead of being ignored.
Examples:
https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64
https://packages.wolfi.dev/os?arch=x86_64
The first URL points to the v3.19 branch of the Alpine repository for the x86_64 architecture, including the main and community components.
Renovate looks up the package in every component you list, and aggregates the releases it finds.
The second URL points to the Wolfi repository, which has no branch or component in its path.
How Renovate builds the index URLs
Say you set this registryUrl in your Renovate config:
{
"packageRules": [
{
"matchDatasources": ["apk"],
"registryUrls": [
"https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64"
]
}
]
}
Renovate then fetches one index per component:
https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
Versioning
This datasource uses apk versioning by default, which follows Alpine's version format (3.2.1-r0, 2.39.0_rc1-r0, 6.5_p20250503-r0) and understands APK's version constraints.
This means a currentValue may be a constraint rather than a plain version, e.g. =~8.12.1 to accept any 8.12.1-rN.
Read the apk versioning docs for the operators it supports.
Depending on which APK repository you are using, you may want to use the loose versioning scheme, like so:
{
"packageRules": [
{
"matchDatasources": ["apk"],
"versioning": "loose"
}
]
}
Usage example
Say you pin Alpine packages in a Dockerfile and want Renovate to bump the versions.
Combine the apk datasource with a regex manager.
Add a custom manager in renovate.json.
The optional branch capture group is filled from the Renovate comment and interpolated into registryUrlTemplate by the regex manager.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["/^Dockerfile$/"],
"matchStrings": [
"#\\s*renovate:\\s*(?:branch=(?<branch>\\S+)\\s+)?depName=(?<depName>\\S+)\\s+ENV .*?_VERSION=\"(?<currentValue>.*)\""
],
"registryUrlTemplate": "https://dl-cdn.alpinelinux.org/alpine?branch={{#if branch}}{{branch}}{{else}}v3.19{{/if}}&components=main,community&arch=x86_64",
"datasourceTemplate": "apk"
}
]
}
The regex manager supplies depName (becomes packageName) and currentValue (the pinned APK version).
The datasource then fetches APKINDEX.tar.gz for each component, finds depName in the index, and compares versions.
Match the registryUrl parameters to your image: the Alpine branch (v3.19 in the default above) and the architecture (x86_64 here, use aarch64 on arm64).
FROM alpine:3.19
# renovate: branch=v3.19 depName=nginx
ENV NGINX_VERSION="1.26.2-r0"
RUN apk add --no-cache "nginx=${NGINX_VERSION}"
depName must match the package name in APKINDEX (the P: field), e.g. nginx for the nginx package.
You can omit branch= in the comment when the template default (v3.19 above) matches your Alpine line.
Multiple Dockerfiles or Alpine versions
The datasource receives one registryUrl per lookup.
Besides the optional branch= pattern in the usage example, you can:
-
Several custom managers with different
managerFilePatterns/matchFilePatternsand a fixedregistryUrlTemplateeach (e.g. one fordocker/alpine-3.18/**, another fordocker/alpine-3.19/**). -
packageRuleswithmatchFileNamesandregistryUrlsto override the parameters for specific paths or packages.
For example, this packageRules entry overrides the registryUrl for the nginx package:
{
"packageRules": [
{
"matchDatasources": ["apk"],
"matchPackageNames": ["nginx"],
"registryUrls": [
"https://dl-cdn.alpinelinux.org/alpine?branch=v3.18&components=main,community&arch=x86_64"
]
}
]
}