Alpine Linux makes a new stable branch from its rolling edge branch every May and November. The main repository of each branch gets security fixes for about two years, while the larger community repository is supported only until the next stable branch arrives. As of 23 October 2026 the current release is Alpine 3.24.3 (21 October 2026), and four branches are supported: 3.24 until 1 June 2028, 3.23 until 1 November 2027, 3.22 until 1 May 2027, and 3.21, which reaches end of support on 1 November 2026.
Alpine is built on musl libc and BusyBox rather than glibc and GNU coreutils, uses its own package manager, apk, and compiles userland binaries as position independent executables with stack smashing protection. Those choices are why its container image is a few megabytes, and also why a binary built on a glibc distribution may not run on it unchanged. This post covers every stable branch with its dates, how the support model actually works, what musl and BusyBox mean in practice, how apk verifies what it installs and what changed in apk-tools v3, how Alpine publishes security fixes, and a step by step routine for checking and upgrading an Alpine base image.
Key takeaways
- Two releases a year, two years of main. The Alpine releases page says a branch is cut from edge "each May and November", the main repository "is typically supported for 2 years" and the community repository "is supported until next stable release". The machine readable releases.json records community end dates six months after each branch.
- Four branches are supported right now. 3.21 (main only, ends 1 November 2026), 3.22 (main only, ends 1 May 2027), 3.23 (main only, ends 1 November 2027) and 3.24 (main and community, ends 1 June 2028). Branches 3.15 through 3.20 are listed as "on request"; 3.14 and older get nothing.
- 3.24 broke the calendar slightly. It branched on 9 June 2026 rather than in May, and its end of support date moved to 1 June 2028 to match.
- Point releases are image rebuilds, not the only way fixes arrive. Fixed packages land in a branch's repository as soon as they are built. A point release such as 3.24.3 rebuilds the installer and container images with those packages, so an image built from an older tag is stale until you run
apk upgradeor rebuild on a newer tag. - The same fix has a different package release on each branch. The musl fix for CVE-2026-107891 is
1.2.6-r3on the 3.24 branch and1.2.6-r6on edge. Compare against the secfixes data for your branch, never against another branch or upstream. - apk-tools v3 shipped in Alpine 3.23. apk 3.0.0 was tagged on 1 December 2025, and Alpine 3.23.0 (3 December 2025) moved to it while keeping the v2 index and package format for now.
- musl is not glibc. Default thread stacks are 128 KiB instead of several megabytes,
dlclosedoes not unload libraries, and the DNS resolver queries all configured nameservers in parallel. Most software is fine; the failures are specific and predictable once you know them. - Alpine does not issue numbered advisories. Fixes are recorded as
secfixescomments in each APKBUILD in the aports repository, published as JSON at secdb.alpinelinux.org, and tracked against CVE and NVD data at security.alpinelinux.org.
This post goes one level deeper than our earlier Alpine coverage. For how apk signs and verifies packages in more depth, see the Alpine apk security model. For a head to head on base image choice, see Alpine versus Debian base image security. If you only need the 3.21 deadline, see Alpine 3.21 support ends 1 November 2026. For sibling distributions, see every Debian release and support date and base image support dates for Debian, Ubuntu and Alpine side by side.
What Alpine Linux is and why containers default to it
The project describes itself on its about page as "an independent, non-commercial, general purpose Linux distribution" for people who value security, simplicity and resource efficiency. Three design decisions define it.
musl libc and BusyBox instead of glibc and GNU tools. The about page says Alpine "is built around musl libc and busybox", that "a container requires no more than 8 MB" and that a minimal disk install needs around 130 MB. BusyBox "combines tiny versions of many common UNIX utilities into a single small executable", so ls, cp, sh, wget and dozens more are one binary with symlinks pointing at it.
Its own package manager and init system. Alpine uses apk (the Alpine Package Keeper) and the OpenRC init system, with script driven setup. There is no systemd. In containers the init system rarely matters, but on hosts it changes how services are managed.
Hardened compilation by default. The about page states that "all userland binaries are compiled as Position Independent Executables (PIE) with stack smashing protection". We look at what the build system actually sets later in this post.
Where it came from
The oldest branch on the official releases table is v2.1, branched on 1 November 2010, and the 2.x series ran until v2.7 (November 2013). Those releases used uClibc. Alpine 3.0.0, released on 4 June 2014, was, in the words of its announcement, "the first release with musl libc instead of uClibc", and it was not ABI compatible with earlier versions. Every release since then has been a 3.x branch built on musl. The project's longer origin story is documented on the Alpine wiki, which was offline when we checked on 23 October 2026, so this post starts the history where the releases table does.
Why it became the default container base
The Docker official image documentation for alpine describes the image as "only 5 MB in size" with access to a package repository "much more complete than other BusyBox based images". Its own example builds a MySQL client image at 36.8 MB on Alpine against about 145 MB for the equivalent Ubuntu image. The official image is maintained by Natanael Copa, an Alpine Linux maintainer, from the alpinelinux/docker-alpine repository.
We unpacked the current x86_64 minirootfs, the tarball the container image is made from, to see what that small size consists of. The 3.24 release manifest lists alpine-minirootfs-3.24.3-x86_64.tar.gz dated 21 October 2026 at 3,704,080 bytes compressed; uncompressed it is about 8.7 MB. It contains exactly 16 packages:
| Package | Version in 3.24.3 | What it is for |
|---|---|---|
| alpine-baselayout, alpine-baselayout-data | 3.7.2-r1 | Directory layout and base config files |
| alpine-keys | 2.6-r0 | Public keys apk trusts for signatures |
| alpine-release | 3.24.3-r0 | /etc/alpine-release and /usr/lib/os-release |
| apk-tools, libapk | 3.0.8-r0 | The package manager and its library |
| busybox, busybox-binsh, ssl_client | 1.37.0-r31 | Shell, core utilities and the TLS helper for BusyBox wget |
| ca-certificates-bundle | 20261020-r0 | Trusted root CA bundle |
| libcrypto3, libssl3 | 3.5.9-r0 | OpenSSL libraries |
| musl, musl-utils | 1.2.6-r3 | The C library, ldd and getconf |
| scanelf | 1.3.9-r1 | ELF inspection tool |
| zlib | 1.3.2-r1 | Compression library |
Sixteen packages is the whole attack surface of a bare Alpine container before you add anything. That is the honest reason security teams like it: fewer packages means fewer components to track and fewer findings to triage. It is not a guarantee of fewer vulnerabilities in what you add on top, and it does not make musl or BusyBox immune to bugs. Both have had CVEs, as the secfixes data below shows.
How the Alpine release model works
Edge, stable branches and point releases
Alpine has one rolling development branch, edge, built from the master branch of the aports repository. Twice a year the project cuts a stable branch from edge: v3.22, v3.23, v3.24 and so on, each with a matching 3.NN-stable git branch in aports. Inside a stable branch, point releases (3.24.0, 3.24.1, 3.24.2, 3.24.3) are snapshots: new ISO images, new minirootfs tarballs and new container image tags built from whatever the branch's repositories contained that day.
The important consequence is that a point release is not the delivery mechanism for fixes. When a maintainer fixes a package in a stable branch, the rebuilt package goes into that branch's repository, and any machine running apk upgrade against it gets the fix immediately. The point release only matters for things built from the release media itself, which mostly means fresh installs and container images. The Alpine user handbook describes a release as "a collection of snapshots of various repositories", which is exactly right.
Recent point releases have been coordinated across every supported branch on the same day. The most recent, announced on 21 October 2026, shipped 3.21.9, 3.22.7, 3.23.7 and 3.24.3 together with an updated ca-certificates package, a musl fix and a batch of OpenSSL fixes. The one before that, on 17 September 2026, shipped 3.21.8, 3.22.6, 3.23.6 and 3.24.2.
main, community and testing
Each branch has repositories with different support promises. The handbook defines them:
| Repository | What it holds | Where it exists | Security support |
|---|---|---|---|
| main | "Officially supported packages that are reasonable to expect to be in a basic system" | edge and every stable branch | About two years from the branch date |
| community | "Packages from testing that have been tested" | edge and every stable branch | Until the next stable branch (recorded as six months in releases.json) |
| testing | "New, broken, or outdated packages that need testing" | edge only | None |
The gap between main and community is the most misunderstood part of Alpine support. In v3.24 on x86_64, the main repository directory held 5,961 package files on 23 October 2026, and community held 22,707. Most of what a typical application image installs from Alpine, from language runtimes to command line tools, may well come from community. On a branch that is past its first six months, those packages are no longer promised security fixes, even though the branch itself is "supported".
The handbook says community is "only supported for 6 months of its respective release". The releases page says "until next stable release". In practice the two agree when releases land on schedule. When 3.24 slipped to June 2026, releases.json still recorded the end of 3.23 community support as 1 May 2026, so for about five weeks the newest community repository with promised support was the one from a branch that had technically aged out of it. That is a corner case, but it shows why the dates in releases.json are the ones to automate against.
Support levels on the releases page
The releases table ends each row with a support level. As of 23 October 2026 there are four values:
- main and community: edge and the newest stable branch (v3.24).
- main: v3.21, v3.22 and v3.23. Only the main repository receives fixes.
- on request: v3.15 through v3.20. The page says "security fixes beyond that can be made on request when there are patches available". This is best effort, not a schedule.
- none: v3.14 and everything older.
Treat "on request" as unsupported for planning purposes. It means a fix may appear if someone asks and a patch exists, not that anyone is watching the branch.
Every Alpine stable branch, from 2.1 to 3.24
The table uses the official releases page and releases.json, fetched on 23 October 2026, for branch dates, last point releases and end of support. The notes column comes from each branch's release announcement on alpinelinux.org. Where the announcement date differs from the branch date in the table, the notes say so.
Supported and recently ended branches
| Branch | Branch date | Latest point release (date) | End of support (main) | Community ended | Support level now | Notable changes |
|---|---|---|---|---|---|---|
| edge | rolling | n/a | n/a | n/a | main and community | Built from aports master; the only branch with testing |
| v3.24 | 2026-06-09 | 3.24.3 (2026-10-21) | 2028-06-01 | 2026-11-01 (planned) | main and community | GRUB 2.14, LLVM 22, Go 1.26; setuptools 82 drops pkg_resources; installer gains Limine and IPv6; GTK+ 3.0 moved to community |
| v3.23 | 2025-12-03 | 3.23.7 (2026-10-21) | 2027-11-01 | 2026-05-01 | main | apk-tools v3; Linux 6.18 (Pi kernels on 6.12); GCC 15; linux-stable replaces linux-edge; planned /usr merge postponed |
| v3.22 | 2025-05-30 | 3.22.7 (2026-10-21) | 2027-05-01 | 2025-11-01 | main | systemd-efistub (stub only) replaces gummiboot for the Secure Boot hook; LLVM 20; Docker 28 |
| v3.21 | 2024-12-05 | 3.21.9 (2026-10-21) | 2026-11-01 | 2025-05-01 | main | Initial loongarch64 support; OpenSSH restarts on upgrade; linux-firmware compressed with zstd |
| v3.20 | 2024-05-22 | 3.20.10 (2026-04-15) | 2026-04-01 | 2024-11-01 | on request | Initial 64-bit RISC-V support; Python 3.12; LLVM 18 |
| v3.19 | 2023-12-07 | 3.19.9 (2025-10-08) | 2025-11-01 | 2024-05-01 | on request | Linux 6.6; EFI_ZBOOT on aarch64 requires a GRUB reinstall |
| v3.18 | 2023-05-09 | 3.18.12 (2025-02-13) | 2025-05-09 | 2023-11-01 | on request | Linux 6.1 with signed kernel modules (not enforced); musl 1.2.4 with TCP fallback in DNS; DT_RELR linking on ppc64le, x86 and x86_64 |
| v3.17 | 2022-11-22 | 3.17.10 | 2024-11-22 | n/a | on request | OpenSSL 3.0 becomes the default; OpenSSL 1.1 via openssl1.1-compat |
| v3.16 | 2022-05-23 | 3.16.9 | 2024-05-23 | n/a | on request | Setup scripts gain NVMe support, admin user creation and SSH key setup |
| v3.15 | 2021-11-24 | 3.15.11 | 2023-11-01 | n/a | on request | New 4096-bit RSA package signing keys (need alpine-keys>=2.4.0-r0); Linux 5.15; mips64 discontinued |
Two date quirks are worth noting. The 3.20 branch reached end of support on 1 April 2026, not 1 May, yet received one more coordinated point release, 3.20.10, on 15 April 2026. And 3.18's end of support is 9 May 2025, the exact two year anniversary of its branch date, rather than the first of a month. Both are on the official table as written.
Older branches (no support)
| Branch | Branch date | Last point release | End of support | Notable changes |
|---|---|---|---|---|
| v3.14 | 2021-06-15 | 3.14.10 | 2023-05-01 | musl enables the faccessat2 syscall; breaks on Docker before 20.10.0 or libseccomp before 2.4.4 |
| v3.13 | 2021-01-14 | 3.13.12 | 2022-11-01 | musl 1.2 makes time_t 64-bit on all architectures (affects armhf, armv7, x86) |
| v3.12 | 2020-05-29 | 3.12.12 | 2022-05-01 | Initial big endian mips64 support |
| v3.11 | 2019-12-29 | 3.11.13 | 2021-11-01 | Linux 5.4 LTS kernel; Raspberry Pi 4; initial GNOME and KDE (announcement dated 2019-12-19) |
| v3.10 | 2019-06-19 | 3.10.9 | 2021-05-01 | iwd, ceph, lightdm (announcement dated 2019-06-13) |
| v3.9 | 2019-01-29 | 3.9.6 | 2020-11-01 | Switch from LibreSSL back to OpenSSL; armv7 support; modloop signed |
| v3.8 | 2018-06-26 | 3.8.5 | 2020-05-01 | End of the hardened kernel based on unofficial grsecurity patches |
| v3.7 | 2017-11-30 | 3.7.3 | 2019-11-01 | |
| v3.6 | 2017-05-24 | 3.6.5 | 2019-05-01 | |
| v3.5 | 2016-12-22 | 3.5.3 | 2018-11-01 | Switch from OpenSSL to LibreSSL; aarch64 support; ZFS as root |
| v3.4 | 2016-05-31 | 3.4.6 | 2018-05-01 | |
| v3.3 | 2015-12-18 | 3.3.3 | 2017-11-01 | |
| v3.2 | 2015-05-26 | 3.2.3 | 2017-05-01 | |
| v3.1 | 2014-12-10 | 3.1.4 | 2016-11-01 | |
| v3.0 | 2014-06-04 | 3.0.6 | 2016-05-01 | First musl release, not ABI compatible with 2.x; full RELRO builds |
| v2.7 | 2013-11-08 | 2.7.9 | 2015-11-01 | Last uClibc branch |
| v2.6 | 2013-05-17 | 2.6.6 | 2015-05-01 | |
| v2.5 | 2012-11-07 | 2.5.4 | 2014-11-01 | |
| v2.4 | 2012-05-02 | 2.4.11 | 2014-05-01 | |
| v2.3 | 2011-11-01 | 2.3.6 | 2013-11-01 | |
| v2.2 | 2011-05-06 | 2.2.3 | 2013-05-01 | |
| v2.1 | 2010-11-01 | 2.1.6 | 2012-11-01 | Oldest branch on the releases table (point releases from 2.1.4) |
The 3.0 announcement said the project planned to "shorten the support period for v3.0 to 1 year instead of the traditional 2 year period" because of the new libc. The releases table nevertheless lists 1 May 2016 as its end of support, about two years after branching. We use the table.
What the pattern tells you
Thirty two stable branches in sixteen years, almost exactly two a year, with end of support dates that settle on 1 May and 1 November. The table also records the big platform shifts in one place: uClibc to musl in 3.0, OpenSSL to LibreSSL in 3.5 and back in 3.9, grsecurity kernels gone in 3.8, 64-bit time in 3.13, stronger signing keys in 3.15, OpenSSL 3 in 3.17, signed kernel modules in 3.18 and apk v3 in 3.23. If you inherit an old Alpine image, the branch number tells you which of those transitions it has not been through yet.
musl, BusyBox and the hardened defaults
What musl changes for compatibility
musl is "an implementation of the C standard library built on top of the Linux system call API" that aims to be lightweight, simple and correct "in the sense of standards-conformance and safety". It has been around for a while: musl 0.5.0 came out on 11 February 2011 and 1.0.0 on 20 March 2014. The current stable series is 1.2.x. Alpine 3.24 ships musl 1.2.6, released on 20 March 2026.
The musl wiki keeps a page of functional differences from glibc. The ones that cause real incidents in containers:
| Area | glibc behaviour | musl behaviour | What breaks |
|---|---|---|---|
| Default thread stack | Based on RLIMIT_STACK, "generally ends up being 2-10 MB" | 128 KiB (80 KiB before musl 1.1.21) | Deep recursion or large stack buffers in worker threads crash with a segfault |
dlclose | Reference counts and unloads libraries | "dlclose is a no-op"; constructors run once, destructors at exit | Plugins that expect a fresh state on reload |
| DNS resolver | Tries nameservers in order, falling through on timeout | Queries up to three nameservers in parallel and takes the first answer | Split DNS setups where nameservers give different answers |
| DNS over TCP | Falls back to TCP for large answers | Only since musl 1.2.4 (Alpine 3.18) | Large DNS responses on Alpine 3.17 and older |
| C locale | Pseudo ASCII | UTF-8 based | Scripts processing non UTF-8 bytes |
time_t on 32-bit | 64-bit opt in | 64-bit everywhere since musl 1.2 (Alpine 3.13) | Prebuilt 32-bit binaries built against old headers |
The thread stack difference is the one that bites most often. A program that runs fine on a glibc distribution, where a worker thread gets several megabytes, can overflow 128 KiB. The musl page notes that since 1.1.21 a program can request a larger default at link time with -Wl,-z,stack-size=N, and many language runtimes already set their own stack sizes. If an application crashes only on Alpine and only under load, check this first.
Binary compatibility is the other big one. A program linked against glibc expects ld-linux-x86-64.so.2 and glibc symbol versions. It will not run on Alpine as is. Alpine's main repository carries gcompat, described in its APKBUILD as a "GNU C Library compatibility layer for musl", which helps some prebuilt binaries start. It is a shim, not glibc, and anything that depends on glibc specific behaviour can still fail. The safer rule is to build native code inside an Alpine stage of a multi stage build so it links against musl.
musl publishes its own advisories on its release history page. In 2026 it listed three CVEs affecting every release through 1.2.6, in the iconv GB18030 decoder, in qsort on 32-bit architectures and in regcomp, and Alpine carried each as a patch in its musl package. A small libc is a smaller surface, not an empty one.
What BusyBox changes
BusyBox applets "generally have fewer options than their full-featured GNU cousins", as its own documentation puts it. In practice that means shell scripts written for GNU tools may use flags BusyBox does not support: sed extensions, grep -P, date parsing formats, find predicates and so on. The default shell is BusyBox ash, not bash. Installing bash, coreutils or grep from main replaces the applets where you need them.
From a security angle, BusyBox concentrates many utilities in one binary, so one fix covers them all, and one vulnerability can touch several applets at once. The secdb entry for busybox on the 3.24 branch lists fixes spanning 2017 to 2025, including a cluster of eleven CVEs fixed in 1.34.0-r0. Because the ssl_client helper and wget applet are part of the base image, BusyBox is also on the network path whenever a build script downloads something without installing curl.
What the hardened defaults actually are
The about page's PIE and stack protector claim is visible in the toolchain. Alpine's gcc APKBUILD configures GCC with --enable-default-pie and --enable-default-ssp, and carries patches that turn on -Wl,-z,now by default, turn on -D_FORTIFY_SOURCE=2 by default and set the default SSP buffer size to 4. The package build tool's default configuration adds -fstack-clash-protection and -Werror=format-security to CFLAGS, sets _GLIBCXX_ASSERTIONS and the libc++ fast hardening mode for C++, and builds Go packages with -buildmode=pie. Release notes add more over time: full RELRO from 3.0, signed modloop from 3.9 and signed kernel modules from 3.18 (verified but not enforced, so third party modules built with AKMS still load).
Two caveats belong next to that list. First, these flags protect binaries that Alpine builds. A Go binary, a Rust binary or a Node.js native addon you build and copy into the image only gets them if your own build sets them. Second, the hardened kernel is gone: 3.8 ended the grsecurity based kernel, and in containers you run on the host's kernel anyway, so the kernel hardening question belongs to the host, not the image.
The /usr merge
An announcement on 1 October 2025 said new installs of 3.23 would be /usr merged, with /bin, /sbin and /lib becoming links into /usr, and that a later release, "possibly 3.26 or 3.27", would make the merge compulsory. The 3.23.0 announcement then said the merge "has been postponed to a later release" and that "a new timeline will be published later". The 3.24.0 notes still warn users with / and /usr on separate filesystems to take care. For container builders, the practical advice from the merge post holds either way: do not install your own files under /bin, /sbin or /lib, use /usr/local, and move any protected_paths entries to their /usr equivalents.
How apk works: repositories, signed indexes and keys
Repositories and the world file
apk reads its repository list from /etc/apk/repositories. In the 3.24.3 minirootfs that file has two lines:
https://dl-cdn.alpinelinux.org/alpine/v3.24/main
https://dl-cdn.alpinelinux.org/alpine/v3.24/community
The branch is part of the URL, which is why moving a host to a new release starts with editing this file. The apk-repositories(5) man page explains the layout: for a v2 repository the signed index is at $base_url/$arch/APKINDEX.tar.gz, for v3 it is $base_url/$arch/Packages.adb, and packages live at $base_url/$arch/$name-$version.apk. A line can start with an @tag, which marks the repository as not used by default; packages from it are only installed when you ask for them with name@tag. The handbook uses @testing for edge's testing repository as its example, and also warns that mixing branches "is discouraged and will cause breakages".
The packages you explicitly asked for live in /etc/apk/world. apk resolves everything else from that list. The handbook calls it "safe to edit by hand", followed by apk add with no arguments to bring the system back into line.
How signatures are checked
Every repository index is signed, and apk refuses an index or package whose signature does not match a trusted key. The trusted keys are files in /etc/apk/keys and /lib/apk/keys, enumerated in that order. The apk-keys(5) man page adds a detail that matters for anyone running a private repository: "APKv2 packages require the filename of public key to match the signing key name in the package. APKv3 files are matched using the public key identity and filename is not significant."
The 3.24.3 minirootfs for x86_64 trusts three keys, all named alpine-devel@lists.alpinelinux.org-*.rsa.pub, installed by the alpine-keys package. The 3.15 release notes introduced "new 4096 bit RSA keys" for package signatures and told users to install alpine-keys>=2.4.0-r0 before upgrading. If you are moving a very old host forward, that is the first prerequisite.
Two options weaken this chain and should not appear in production builds. The apk(8) man page documents --allow-untrusted, which will "install packages with untrusted signature or no signature", and the SSL_NO_VERIFY_HOSTNAME environment variable, which "disables the server certificate name verification". Index signatures still protect you when TLS is weakened, but --allow-untrusted removes the signature check itself. A --keys-dir override, also documented, replaces the system key directories entirely, so use it only when you mean to trust a different set of keys.
What changed in apk-tools v3
apk-tools v3.0.0 was tagged on 1 December 2025, and Alpine 3.23.0 shipped with it two days later. The 3.23 announcement described the move as upgrading the package manager to v3 "but keeping the v2 index and package format for now". So on 3.23 and 3.24 you run the v3 tool against v2 format repositories. The release notes list what v3 adds and removes:
| Area | apk-tools v3 change |
|---|---|
| Formats | New package and index format (ADB) "designed for improved security, performance and extensibility", with newer hash and signature algorithms and zstd compression |
| Removed | FTP URLs, original "alpine v1" packages, broken v2 packages from foreign tools, and MD5 hashes |
| Architecture | Package arch is validated and must be compatible to install |
| Scripts | Script output is prefixed with * ; scripts run from a memfd where possible, so use $APK_SCRIPT instead of $0 |
| Configuration | Variables in the repositories file; a config file for default options; defaults can be shipped in /lib/apk |
| New commands | mkpkg, mkndx, adbsign, adbdump, and query (a superset of info, list and search with JSON and YAML output) |
| Logging | Write transactions are logged to /var/log/apk.log |
| Other | --usermode chroots as non-root, --cache-preload, --preupgrade-depends, mbedtls as an alternative crypto backend |
The notes also lay out a staged migration to the new formats: publish the new index next to the old one, then switch repository components one at a time, starting with something like testing. They say the next major release is expected to refactor the installed database format too. Since v3.0.0 the project has shipped point releases through v3.0.8 (31 August 2026), while still maintaining the v2 line (v2.14.12 on 16 September 2026) for older branches.
For container builders, three things matter. If you pin apk-tools or call libapk directly, test on 3.23 or later. If your package install scripts relied on $0, they need $APK_SCRIPT. And /var/log/apk.log is a new file that ends up in image layers if you do not clean it, which is harmless but shows up in diff based reviews.
How Alpine publishes security fixes
Alpine has no numbered advisory series like Debian's DSAs. The record of what was fixed lives in three connected places.
secfixes in aports
aports is the git repository of build recipes (APKBUILD files) for every package. When a maintainer fixes a CVE, they add a secfixes comment block to the APKBUILD that maps package versions to the CVEs they fix. The musl recipe on the 3.24-stable branch currently reads, in part:
# secfixes:
# 1.2.6-r3:
# - CVE-2026-107891
# 1.2.6-r2:
# - CVE-2026-40200
# 1.2.6-r1:
# - CVE-2026-6042
# 1.2.5-r10:
# - CVE-2025-26519
On master, which builds edge, the same CVE-2026-107891 fix is recorded against 1.2.6-r6, because edge had extra package rebuilds in between. The upstream version (1.2.6) is the same, the fix is the same, and the Alpine package release number is different. Anyone comparing versions across branches, or comparing the upstream number to an advisory, will get this wrong.
secdb: the JSON feed
secdb.alpinelinux.org publishes the secfixes data as JSON, one file per branch and repository, for every branch back to v3.2. Each file names the branch, the repository, the architectures and a URL template, then lists packages with their secfixes. On 23 October 2026, v3.22/main.json covered 275 packages with 3,463 CVE entries in total, and v3.24/main.json covered 289 packages. The directory listing showed the v3.21 through v3.24 and edge files updated that same day, while v3.14 was last touched in April 2026 and v3.2 to v3.8 in 2021.
This feed is what most container scanners read when they decide whether an Alpine package is fixed. The logic is simple: if the installed version is equal to or newer than the version listed against a CVE for that branch, the CVE is fixed. Some entries are recorded against version 0, which by convention means the packaged version was never affected. If a scanner reports a CVE on an Alpine package and the secdb shows a fix at a lower version than you have, the scanner is reading the wrong branch or not reading secdb at all.
The security tracker
security.alpinelinux.org is the issue tracker built on top. Its home page says it is "derived from the CVE dictionary, the NVD database and Alpine's own security fixes database", and it lists potentially vulnerable packages and orphaned packages per branch and repository, from edge down through each supported release. It tells packagers that if a package "erroneously appears vulnerable", they should add secfixes data, and that the tracker should update within 24 hours of corrected data being published. Use it to check whether a known CVE is still open on your branch.
Release announcements
Coordinated point release posts on alpinelinux.org name the headline security fixes. The 21 October 2026 post, for example, listed an updated ca-certificates package "addressing the eMudhra intermediate CA compromise", a musl CVE and nine OpenSSL CVEs from the OpenSSL advisory of 29 September 2026, noting which did not apply to 3.21. These posts are the closest thing Alpine has to an advisory, but they cover only what went into the release images, not every package fix.
Walkthrough: check the branch of a container and upgrade it safely
This walkthrough assumes Docker and jq on your workstation. Replace myapp:prod with your image.
Step 1: find the Alpine branch inside the image
# The point release the image was built from
docker run --rm --entrypoint cat myapp:prod /etc/alpine-release
# The same in os-release form
docker run --rm --entrypoint cat myapp:prod /etc/os-release
# Which repositories apk will use, which tells you the branch
docker run --rm --entrypoint cat myapp:prod /etc/apk/repositories
/etc/alpine-release gives a version such as 3.21.4. The branch is the first two parts, v3.21. The repositories file confirms it, and it also tells you if someone has added edge or testing repositories, which you want to know about.
If the image has no shell or no cat because a later stage removed them, inspect the filesystem instead:
docker create --name inspect myapp:prod
docker cp inspect:/etc/alpine-release - | tar -xO
docker rm inspect
Step 2: look up the end of support date
BRANCH=v3.21
curl -s https://alpinelinux.org/releases.json \
| jq -r --arg b "$BRANCH" '.release_branches[]
| select(.rel_branch == $b)
| "\(.rel_branch) branched \(.branch_date), main ends \(.eol_date), community ends \(.repos[] | select(.name=="community") | .eol_date)"'
For v3.21 this prints a main end date of 2026-11-01 and a community end date of 2025-05-01. The same JSON has a latest_stable field, which was v3.24 on 23 October 2026. A CI job that runs this lookup weekly and fails when any image's branch is within 60 days of its end date is cheap and catches the problem before the deadline does.
Step 3: see which installed packages come from community
docker run --rm --entrypoint sh myapp:prod -c \
'apk update >/dev/null && apk list --installed' | sort > installed.txt
On apk v3 you can also use the new apk query applet, which can output JSON. Cross check the package names against the community index for your branch. Anything from community on a branch older than six months has had no promised security support since that date.
Step 4: see what is already fixed but not installed
docker run --rm --entrypoint sh myapp:prod -c \
'apk update >/dev/null && apk upgrade --simulate'
Every line in that output is a package with a newer build in the branch repository. If the list includes musl, OpenSSL, BusyBox or ca-certificates, the image is behind the branch, and rebuilding on the latest point release tag (or adding apk upgrade --no-cache to the Dockerfile) closes the gap without changing branch.
To check a specific fix against the branch's secdb:
curl -s https://secdb.alpinelinux.org/v3.21/main.json \
| jq '.packages[] | select(.pkg.name == "musl") | .pkg.secfixes'
Step 5: check for glibc assumptions before you move
Before changing the branch, find anything in the image that was not built by Alpine. scanelf is already in the base image:
docker run --rm --entrypoint sh myapp:prod -c \
'scanelf --nobanner --recursive --format "%i %F" /usr/local /opt /app 2>/dev/null'
The %i field shows each binary's interpreter. Anything that points at /lib64/ld-linux-x86-64.so.2 was linked against glibc and is only running because of gcompat or not running at all. Rebuild those in an Alpine build stage before the upgrade, not after.
Step 6: move the base image to a supported branch
Change the FROM line to a specific supported branch and pin it by digest so a rebuild is reproducible:
docker buildx imagetools inspect alpine:3.24
# Before
FROM alpine:3.21
# After: branch tag for readability, digest for reproducibility
FROM alpine:3.24@sha256:<digest from imagetools inspect>
RUN apk upgrade --no-cache \
&& apk add --no-cache ca-certificates tzdata
Docker Hub tags for supported branches on 23 October 2026 were 3.24.3, 3.24, 3 and latest for the newest branch, then 3.23.7, 3.22.7 and 3.21.9. Pin the minor branch (3.24), not 3 or latest, so that the next stable branch does not arrive in your build unannounced. Then let a dependency update tool move the digest when a new point release lands.
Read the release notes for every branch you skip. Going from 3.21 to 3.24 means reading the 3.22, 3.23 and 3.24 notes: the Secure Boot stub change, apk-tools v3 and the setuptools pkg_resources removal are each in a different one. Python projects that still import pkg_resources will fail on 3.24 until they migrate.
Step 7: rebuild, test and compare
docker build -t myapp:candidate .
docker run --rm --entrypoint cat myapp:candidate /etc/alpine-release
docker run --rm myapp:candidate /app/healthcheck
Run your test suite against the candidate image, then compare what changed. If you use Safeguard, the CLI can scan both images and give you component and vulnerability lists to diff:
safeguard scan --image myapp:prod
safeguard scan --image myapp:candidate
Results are written as JSON under ./output, so a short jq comparison shows which components and findings came and went with the base image change.
Upgrading an Alpine host instead of an image
The handbook's procedure for a host is to edit /etc/apk/repositories to the new branch and run apk upgrade --available. Every release announcement repeats the same advice. Take a snapshot or a tested backup first, because apk has no downgrade path: the handbook says downgrading "is currently not supported".
cat /etc/alpine-release
apk info -v alpine-keys # must be 2.4.0-r0 or newer before crossing 3.15
sed -i 's#/v3\.21/#/v3.24/#g' /etc/apk/repositories
apk update
apk upgrade --available
sync
Warning: the 3.24 release notes say GRUB users "must run grub-install" after upgrading so the new GRUB is installed to disk. Pointing grub-install at the wrong disk overwrites that disk's boot code. Check the device path first and confirm it is the disk the system boots from:
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
cat /proc/cmdline
Then run grub-install with the device or EFI options you have just confirmed, and reboot. On aarch64, the 3.19 notes add that EFI_ZBOOT kernels require reinstalling GRUB with grub-install --bootloader-id=alpine --efi-directory=/boot.
Alpine, Debian slim and distroless style images compared
The three common minimal base image styles make different trade offs. The figures below come from each project's own documentation; image sizes vary by architecture and tag.
| Property | Alpine | Debian slim | Distroless style (Debian based) |
|---|---|---|---|
| C library | musl | glibc | glibc in base images; none needed in static |
| Package manager in image | apk | apt and dpkg | None |
| Shell | BusyBox ash | dash and bash | None, except debug tags with a BusyBox shell |
| Packages in base | 16 in the 3.24.3 minirootfs | Debian "required" packages, built from the minbase variant, minus docs and man pages | Only the application's runtime dependencies |
| Published size | About 5 MB (Docker docs); 3.7 MB compressed minirootfs | Larger than Alpine; the Debian docs describe slim as an experiment in a slimmer base | static-debian13 around 2 MiB per the project README |
| Release cadence | New branch each May and November; point releases about every one to three months | Debian stable roughly every two years, image tags rebuilt regularly (dated tags such as bookworm-20261020-slim) | Follows Debian releases |
| Security support length | About 2 years for main; community until the next branch | About 3 years from the Security Team plus 2 years of LTS (five years in total) | Tied to Debian; for Debian 13, static and base until one year after Debian 14, language images until three months after |
| Fix data for scanners | secdb JSON and the Alpine tracker | Debian Security Tracker, DSAs and DLAs | Debian's data, since packages are Debian's |
| Compatibility risk | glibc linked binaries; small thread stacks; BusyBox tool flags | Lowest for software built on mainstream Linux | No shell for debugging; you must copy in everything you need |
| Debugging inside a running container | Shell and apk available | Shell and apt available | Needs a debug tag or an ephemeral debug container |
Sources for the table: the Docker alpine image docs, the Docker debian image docs on the slim variant and minbase, the distroless README and support policy, and Debian's releases page for the five year lifecycle.
How to choose, briefly:
- Short support windows are the main cost of Alpine. Two years for main means a base image upgrade roughly every year if you want headroom. If your team cannot commit to that cadence, a base with a five year lifecycle may cost less overall.
- musl is the main compatibility risk. Teams shipping Python with many compiled wheels, Java with native agents, or vendor binaries usually find glibc bases simpler. Statically linked Go and Rust binaries are generally indifferent.
- Distroless style images remove the package manager entirely. That shrinks what an attacker can use after a compromise, but you lose the ability to run
apk upgradein a running container, which you should not be doing anyway. Fixes come from rebuilding. - Small is not the same as patched. A 16 package Alpine image from a 3.18 tag is smaller than a Debian slim image and far less supported. Support status matters more than size.
Common mistakes
- Treating
alpine:3oralpine:latestas a stable pin. Both move to the next branch when it ships. Pinalpine:3.24and a digest, and move them on purpose. - Assuming the whole branch is supported for two years. Only main is. Community support stops when the next branch arrives, and many runtime packages live in community.
- Reading "on request" as supported. Branches 3.15 to 3.20 get fixes only if someone asks and a patch exists. Plan as if they were end of life.
- Comparing package versions across branches. The same musl fix is
1.2.6-r3on 3.24 and1.2.6-r6on edge. Compare against secdb for your own branch. - Never running
apk upgradein the build. A point release image is a snapshot. Fixes land in the repository between point releases, and an image built from a tag withoutapk upgrademisses them until the next tag. - Adding edge or testing repositories to a stable image to get one newer package. The handbook warns that mixing branches "will cause breakages", and testing has no security support. Use a newer stable branch instead.
- Copying glibc binaries into an Alpine image. Even with
gcompatthey can fail in subtle ways. Build native code in an Alpine stage. - Ignoring the thread stack limit. Intermittent segfaults under load on Alpine but not on Debian often trace to the 128 KiB default thread stack.
- Using
--allow-untrustedto get past a signature error. The error is telling you the key or the package is wrong. Fix the key in/etc/apk/keysor the repository, not the check. - Skipping release notes across several branches. A jump from 3.21 to 3.24 crosses the apk v3 switch, the Secure Boot stub change and the setuptools change. Each is in a different announcement.
- Forgetting the bootloader after a host upgrade. On 3.24, GRUB users must reinstall GRUB after upgrading, and on aarch64 with EFI_ZBOOT since 3.19, the same applies.
FAQ
When does Alpine 3.21 reach end of life?
Alpine 3.21's main repository reaches end of support on 1 November 2026, according to the official releases page. Its community repository already ended on 1 May 2025. After that date, plan on no fixes at all for 3.21 images and move to 3.22 or later. 3.22 is supported until 1 May 2027 and 3.24 until 1 June 2028.
How long is an Alpine Linux release supported?
The main repository of each stable branch is typically supported for two years from its branch date. The community repository is supported until the next stable branch, which is about six months. After main support ends, a branch may move to "on request" status for a while, where fixes are made only when someone asks and a patch exists.
What is the latest version of Alpine Linux?
As of 23 October 2026 the latest stable branch is v3.24, and the latest point release is 3.24.3, published on 21 October 2026. Alpine cuts new branches each May and November, so the next branch is expected around November 2026, though 3.24 itself arrived in June rather than May.
What is the difference between Alpine main and community?
main holds officially supported packages expected in a basic system and is supported for about two years per branch. community holds packages that have graduated from testing and is supported only until the next stable branch. Check where your installed packages come from, because community packages lose support long before the branch does.
Is Alpine edge safe to use in production?
edge is a rolling branch built from aports master. The handbook calls it "stable enough for daily use" but less stable than release branches, and it has no support window. Its versions move constantly, which makes reproducible builds harder. Use a stable branch for production images.
Does Alpine Linux use glibc?
No. Alpine uses musl libc, and has done so since 3.0 in 2014. Before that it used uClibc. Binaries linked against glibc will not run unchanged. The gcompat package in main is a compatibility layer that helps some of them, but building natively against musl is more reliable.
What changed in apk-tools v3?
apk-tools 3.0.0 was released on 1 December 2025 and became Alpine's package manager in 3.23.0. It adds a new signed package and index format, a query command with JSON output and transaction logging, and drops FTP URLs and MD5 hashes. Alpine still serves the v2 index and package format for now.
Where does Alpine publish security advisories?
Alpine has no numbered advisory series. Fixes are recorded as secfixes in each APKBUILD in aports, published as JSON per branch at secdb.alpinelinux.org and tracked on security.alpinelinux.org. Point release posts list the headline fixes.
How do I check which Alpine version a Docker image uses?
Run docker run --rm --entrypoint cat <image> /etc/alpine-release to get the point release, such as 3.21.4, and read /etc/apk/repositories to confirm the branch. Then look up that branch's end date on the releases page or in releases.json.
Can I upgrade Alpine across several versions at once?
The handbook describes editing /etc/apk/repositories to the newer branch and running apk upgrade --available, without requiring one branch at a time. Read the notes for every skipped branch, check alpine-keys if you cross 3.15, and take a snapshot first, because downgrades are not supported.
Alpine's own packages get their fixes from the Alpine maintainers through the branch repositories, and nothing below changes that or replaces an Alpine base image upgrade. But every Alpine based image also carries the application dependencies you install on top, from npm, PyPI, Maven or Go modules, and those are pulled again on every rebuild, so they need their own inventory and their own fix path.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo sets up the local runner on your own machine, approves it with its device code, then scans from the terminal. On a build host that produces Alpine based images, that is the route for scanning the repositories and images you build there, for example to list what a 3.21 image contains before you rebase it on 3.24.
Click through it at your own pace, or open it full screen.
The second demo is a real Safeguard CLI session: asked to fix a lodash CVE, the assistant reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. The same flow applies to a Node.js service running on an Alpine base, where the vulnerable dependency is in your repository rather than in an Alpine package, so the fix is a change you make and test yourself.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. Once your projects are in Safeguard, that connection lets you ask which of them contain a given component and version, which is the question you need answered when a branch such as 3.21 reaches end of support and you want every image still built on its musl and OpenSSL packages.
Click through it at your own pace, or open it full screen.
To try the CLI, download Safeguard and follow the CLI documentation.
Quick check
An image runs Alpine 3.22 and installs nodejs from the community repository. Is nodejs getting security fixes in October 2026?
No. The 3.22 branch's main repository is supported until 1 May 2027, but its community repository ended on 1 November 2025. Rebase the image on 3.24, whose community repository is still supported, or install the runtime another way you can keep patched.
A scanner flags musl CVE-2026-107891 in an Alpine 3.24 image with musl 1.2.6-r3 installed, because the edge APKBUILD lists the fix at 1.2.6-r6. Is the image vulnerable?
No. On the 3.24 branch the secfixes data records the fix at 1.2.6-r3, so that package is fixed. The scanner is comparing against the wrong branch. Check the v3.24 file on secdb.alpinelinux.org.
Sources
- Alpine Linux release branches
- Alpine releases.json
- About Alpine Linux
- Alpine news feed
- Alpine 3.0.0 released
- Alpine 3.5.0 released
- Alpine 3.8.0 released
- Alpine 3.9.0 released
- Alpine 3.10.0 released
- Alpine 3.11.0 released
- Alpine 3.12.0 released
- Alpine 3.13.0 released
- Alpine 3.14.0 released
- Alpine 3.15.0 released
- Alpine 3.16.0 released
- Alpine 3.17.0 released
- Alpine 3.18.0 released
- Alpine 3.19.0 released
- Alpine 3.20.0 released
- Alpine 3.21.0 released
- Alpine 3.22.0 released
- Alpine 3.23.0 released
- Alpine 3.24.0 released
- Alpine 3.21.9, 3.22.7, 3.23.7 and 3.24.3 released
- Alpine /usr merge announcement
- Alpine 3.24 x86_64 release manifest
- Alpine user handbook: working with apk
- apk-tools v3.0.0 release notes
- apk-tools releases
- apk(8) man page
- apk-repositories(5) man page
- apk-keys(5) man page
- aports musl APKBUILD, 3.24-stable
- aports musl APKBUILD, master
- aports gcc APKBUILD
- aports gcompat APKBUILD
- abuild default.conf
- Alpine secdb
- Alpine secdb v3.22 main.json
- Alpine secdb v3.24 main.json
- Alpine security tracker
- musl libc
- musl release history and advisories
- musl: functional differences from glibc
- About BusyBox
- Docker official image docs: alpine
- Docker official image docs: debian
- Distroless README
- Distroless support policy
- Debian releases
Last reviewed: 23 October 2026. Alpine support dates and point release numbers change with each release; check the Alpine releases page and releases.json before planning an upgrade.