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 (APKBUILD
s) 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 PKGBUILD
s 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 APKBUILD
s 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.
Subpackages
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.
postmarketOS
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™.