‹ back home

In praise of Alpine and apk

2023-02-18 #alpine #linux #open-source

Since the change of year, I’ve been using Alpine Linux on my main computing device (a new desktop PC that I assembled in December). These are some notes on in, some niceties and caveats.

I used ArchLinux for over a decade before, so keep in mind that my main point of reference/background is using Arch+pacman. However, this is not an “Arch vs Alpine” article.

My woes with package management

Over the years, I’ve experimented with many ways of “keeping a list of the list of packages installed”. The main goal is to be able to quickly recreate my installation, and also understand how it changed over time (e.g.: being able to roll back to a previous state with just the list of packages), but I also very much prefer a declarative approach to installed packages (rather than an imperative approach).

The idea behind a declarative approach is to basically have a list of packages I want installed, and let the package manager install/remove anything so that installed package match exactly that list (plus any required dependencies, of course).

Over the last couple of years I tried a lot of approaches for this. One such approach was a couple of scripts that analysed system state and added/removed packages to make it converge to the desired state. This kind of worked, but meant a lot of maintenance and operational overhead and was orthogonal to how pacman was designed to be used. I also tried having a meta-package, which has my “list of wanted packages” as dependencies, and then remove anything no required my it. Again, I needed extra scripts and complexity on top of the package manager itself.

I’ll mention that I also tried NixOS, but found it to be far too complex and too distant from the KISS philosophy for my taste.

apk and /etc/apk/world

apk (literally “alpine package keeper) is Alpine’s package manager. I was very pleased to learn about apk’s /etc/apk/world.

In essence, this file is a list of desired packages, and apk will install and uninstall packages so that the system state converges to the list. Exactly what I’d been looking for, but built right into the package manager. Actually it’s not just built into the package manager: it’s the very foundation of how it works. apk add actually adds a package to that list, and then installs any that are not present. apk del removes a package from that list, and only uninstalls it if it’s not a dependency for anything else.

Understanding this also explains why apk uses add and del rather than the more traditional install and uninstall. I extremely pleased by this design and how well it works. Additionally, one can edit the world file manually and run apk fix, which will install/uninstall any packages required to converge to the declared list.

I also admit it’s a breath of fresh air to have “normal” verbs as subcommands rather than pacman -Syu, even if I already know of most pacman’s flags.

The edge branch

Alpine has releases every six months (each called a “branch”, e.g.: the “3.17 branch”), but also has a special branch called edge. Alpine edge is basically a rolling release with the latest versions of packages. I don’t really like the idea of point releases for a desktop system, and very much prefer a rolling release distro, and Alpine edge is exactly that.

Software is updated and rolled out continuously on edge, and I really care about this, especially given that I’ll often submit patches (or just bug reports) upstream to all kinds of projects, and want to see these changes on my system today, not in a few months.

The aports tree

All of Alpine’s package definitions (APKBUILDs) are kept in a single git repository called aports. This approach that is apparently becoming more and more common these days across distributions, and I can understand why: it makes maintenance a lot easier, and makes fetching the entire ports tree very simple (just a single git clone).

APKBUILD files look extremely similar to PKGBUILD files, so learning how they work was trivial. They’re actually so similar, that it bothers me to know that they’ll likely never converge into one single format. The main difference is that APKBUILD files are sh scripts, and PKGBUILDs are bash scripts (and rely on several bash-specific feature), but there are other differences too.

The tooling for building packages is substantially different from Arch’s. I dare say it’s a bit more straightforward and simpler. I’m honestly feeling comfortable with abuild very quickly, though it does bother me a lot that it doesn’t have a man page.

Given that the ports tree is a single git repository hosted on Alpine’s GitLab instance, anybody can submit patches very easily (via Merge Requests), which makes contributing back very easy for anyone interested.

The testing repository

Aside from the usual packages, Alpine also has a testing package repository. Packages in this repository are contributed and maintained by any member of the community. I’ve already submitted a few and am actually pretty happy with how easy it is to contribute. Submissions are reviews by a human to check the quality of the package and if they’re good quality get merged in.

Binary packages become available shortly after change to APKBUILDs being merged.

This somewhat reminds me of the AUR, though it’s not quite the same. Alpine’s testing repository is more curated (e.g.: there’s a review process), which has both its upsides and downsides. It implies that package quality have quite a bit more oversight, but it also implies you won’t find packages for patched versions of software, or *-git packages (ones that build from the latest upstream source). It’s easy to patch an APKBUILD locally and build a package oneself, though a bit more work than finding a PKGBUILD where somebody has already done it for you.


Packages can contain subpackages (something that’s definitely not exclusive to Alpine). However, there’s a few extra features built on top of them which I’ve found very nice.

First of all, if a package contains man pages and a *-doc subpackage, the man pages are automatically moved into that subpackage. If a package contains *-zsh-completion or *-bash-completion subpackage, the completion files are moved into those. This keeps package sizes minimal, while making these extras available to anyone who wants them.

The second feature is what makes it all round up though. If I install the docs metapackage, then the *-doc subpackage is installed for any package that I have installed. In other words, if I install docs and darkman, then darkman-doc (e.g.: it’s man page) is automatically installed. And so is every other man page relevant to my setup.

Since I have zsh installed, all the *-zsh-completion subpackages are installed too – so anything that I install automatically has zsh completion.

This strikes a great balance, where it’s trivial to make a tiny Alpine installation with no extras (e.g.: you don’t want man pages on docker images or embedded systems), but it’s very easy to get these extras on a desktop or laptop installation.

And it’s all very nicely designed and works very seamlessly (this features uses an install_if= field).

glibc vs musl

Something that makes Alpine really different is that it uses musl instead of glibc. musl focuses on being lightweight, fast, simple and standards-compliant. This results in a few practical implications:

The first is that using a different libc helps finds bugs in programs that assume that every system uses glibc. This helps write portable software and spot portability issues faster.

The second notable difference, is that binaries that link to glibc won’t run. This is mostly a pain for proprietary software, and, in particular, Steam. Steam simply won’t run natively on Alpine, although there’s two workarounds: (1) running it in a chroot/container with another Linux distro, or (2) running it via Flatpak. I opted for the second choices, and it works fine without any further hassles.

The third and final difference, is that musl has not multilib support. This means no support for running 32-bit binaries on 64-bit systems. Again, this realistically only affects proprietary software, which you can’t rebuild for another architecture. In particular, this can be a nuisance for games ran via wine, where installers (and launchers) might be 32-bit, but the game itself 64-bit. The workaround are the same as above, and I use Bottles via Flatpak as a workaround which has also had no issues. Flatpak is, indeed, a great solution to running proprietary software on Linux (just games in my case).

Note that the wine situation is likely to change in future, since wine is looking to avoid using native 32-bit dependencies for 32-bit binaries and seeks to solve the interoperability issue internally.

Alpine is bare bones

Alpine installs just a base system out of the box and feels a bit like installing a BSD more than a typical Linux distro. It’s up to the user to install anything that’s needed, and this is one of the reasons it’s so popular as a base for docker images and servers: it’s super minimal and small by default.

The default shell, login, grep and other basic Unix utilities are provided by BusyBox. It’s possible to install pam, udev, dbus and polkit , but it needs to be done explicitly (most of it is pretty straightforward). I plan on a separate article soon with all my notes on setting up a new desktop system.

Regular applications work as usual

Normally desktop applications (e.g.: swaywm, firefox, foot, neovim, etc) all work the same with not issues. There’s nothing special to mention here; it’s still just another Linux distro after all.

Supported architectures

Alpine supports quite a few architectures, including ARM and RISCV. While ArchLinuxARM (a.k.a.: ALARM) exists, I’ve never quite been convinced by the development model. ArchLinux and ArchLinuxARM don’t work as a single project, but feel more like a project and its fork. Patches on one aren’t unstreamed to the other nor made in an upstream-friendly and ALARM didn’t feel to open to contributions. I found obvious packaging issues, but never managed to collaborate to get them fixed (and a lot of the build infrastructure is kind of a mystery). I ran one system with Arch and another with ALARM for many months, and I felt like I was running two entirely different distros (technically, they are different distros).

Alpine supports multiple architectures upstream, so even packages that I submit to testing are immediately available as binary packages on ARM.


Finally, I’ve been experimenting with postmarketOS recently quite a bit. It’s an Alpine-based Linux distribution for smartphones (see their list of devices).

Using Alpine and pmOS has better synergy. Scripts and configurations that I write for one work on the other, and the package manager is the same for both.

More details on this are a bit out-of-scope, but I’ll have a separate article on pmOS and my history with #LinuxMobile soon™.

Have comments or want to discuss this topic?
Send an email to ~whynothugo/public-inbox@lists.sr.ht (mailing list etiquette)

— § —