Alpine Linux

Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.

Motivation

The main reason for moving to Alpine Linux from Arch was to avoid the all or nothing method of updating packages. Since i was taking btrfs snapshots, i could see 3GB+ updates every month, which i felt was excessive compared to my needs. Around this time, i was also trying to replaced the default OS on the Pi Zero 2W due to 512M memory.

Migration path

In our home computers , i installed this OS to homepc2 along with Arch . I have also configured it such that both are identical in almost all functionalities. I intend to use this with pi instead of the default OS in it.

A few months back i.e around mid-april 2025, totally nuked the Arch installation.

The biggest negative is that proprietary software will not work on Alpine, unless the binary is released by the vendor for Alpine Linux.

For the above issue, a debian chroot has been configured with bwrap by following the Alpine Wiki. Tested working with VS Code, Obsidian, google-chrome etc..

Taking an inspiration from Arch wiki, i have been updating Alpine Wiki↗ as part of giving back something to the community.

Found that gcompat can provide a compatibility to glibc and tested to find that it really works. I have updated the wiki of Alpine Linux mainly by re-organizing and existing content.

The configuration in Alpine is nearly identical to earlier setup i.e swaywm , hugo_server , rsync , rclone , btrfs filesystem with btrbk , Refind , Emacs , Beancount etc.

I have managed to get the Canon LBP2900 also configured manually as no package exists for it in Alpine Linux. As of 2024-12-04, Canon installation is not complete. At some point, the manual driver installation was completed drivers works great here.

Installed shotwell as lightweight photo manager to view timeline based photos.

replaced cronie by dcron

Earlier i was using anacron from cronie for running backup for btrbk and Beancount . However, recently understood that dcron does this elegantly with @daily directive.

So moved the entries in etc/anacrontab to /var/spool/cron/crontabs/root as follows:

# min   hour    day     month   weekday ID 			command
*/15    *       *       *       *       			run-parts /etc/periodic/15min
@hourly         			ID=periodic.hourly      run-parts /etc/periodic/hourly
@daily          			ID=periodic.daily       run-parts /etc/periodic/daily
@weekly         			ID=periodic.weekly      run-parts /etc/periodic/weekly
@monthly        			ID=periodic.monthly     run-parts /etc/periodic/monthly
@daily 					ID=daily-btrbk		sleep 420 && /usr/bin/btrbk -q -c /etc/btrbk/btrbk.conf run
@daily					ID=daily-beanbk		sleep 240 && /data/myhome/prabu/linux_config/scripts/backup_beancount 2>&1 | logger -t daily-beanbk
@weekly					ID=weekly-fstrim	sleep 720 && /sbin/fstrim -v /mnt/btrfs 2>&1 | logger -t weekly-fstrim

sending emails meant for root to gmail

To send and receive emails meant for root to a gmail account, installed and configured msmtp as MTA/SMTP client and mailx as mail user agent(MUA). Configured msmtp by copying the /etc/msmtprc from Openwrt .

$ cat /etc/aliases
root: kXXXX@gmail.com
$ ls -l /usr/sbin/sendmail
lrwxrwxrwx    1 root     root            14 Oct 10 14:15 /usr/sbin/sendmail -> /usr/bin/msmtp*
$ ls -l /etc/msmtprc
-rw-------    1 root     root          1580 Oct 10 14:11 /etc/msmtprc

Using edge as a tagged repository

Initially kept edge main and community as tagged repositories along with stable as apk-tools v3 was in edge only during mid'25.

Found the following solution to install a package without enabling the repository in IRC:

# apk add --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main apk-tools

Well the above option fails, when time for upgrade comes. The package automatically goes to the untagged repository. So i restored back edge in /etc/apk/repositories and due to various other issues, went back to apk-tools v2 and removed the edge repositories.

OS upgrades

On 2024-12-04, the first OS upgrade from 3.20 to 3.21 was done. Followed the progress through RC1 to RC5 as based on wiki instructions using sed to change the version in /etc/apk/repositories file.

Dateupgraded OS versionComments
2024-12-04v3.21First upgrade
2025-05-30v3.22
2025-12-03v3.23
2025-06-09v3.24venv changes required due to python upgrade

Python venv

Due to python upgrade, venv was broken for beancount and graph generation scripts. Deleted the old venv and recreated them and then installed the packages. The venv are located at data/myhome/prabu/org.venv and data/myhome/prabu/bean_venv, with the later symlinked from /data/myhome/prabu/beancount.venv.

graph generation script uses the following packages:

  • fa2
  • networkx
  • python-louvain

beancount venv uses the following packages including some for importers:

  • fava
  • msoffcrypto-tool
  • openpyxl
  • smart_importer

So the steps are:

rm -rf /data/myhome/prabu/org/.venv
python3 -m venv /data/myhome/prabu/org/.venv
source /data/myhome/prabu/org/.venv/bin/activate.fish
pip install fa2 networkx python-louvain

Earlier used a upgrade driver for the above adapter in Arch . When i moved to Alpine Linux it was working out of the box, so never bothered until today. Unable to connect from any android mobile as in termux arp it shows as “incomplete wlan0”.

On further troubleshooting found that the default driver rtl8xxxu has this known behavior of not working with Android mobiles.

After installing the driver from https://github.com/clnhub/rtl8192eu-linux↗ the issue got resolved.

TL-WN823Nv2/v3 is supported by the above driver.

[Sat Aug 23 13:28:27 2025] 8192eu: loading out-of-tree module taints kernel.
[Sat Aug 23 13:28:27 2025] 8192eu: module verification failed: signature and/or required key missing - tainting kernel
[Sat Aug 23 13:28:27 2025] usbcore: registered new interface driver rtl8192eu

The loading of the driver was automated using the below script:

cat /etc/modprobe.d/rtl8192eu.conf
# Blacklist conflicting drivers
blacklist rtl8xxxu
# Auto-load new driver
install rtl8192eu /sbin/modprobe --ignore-install 8192eu

However still the WiFi module loading failed when kernel got upgraded. So added a script to recompile and load the driver was added as apk commit-hook

ls -l /etc/apk/commit_hooks.d/rebuild-rtl8192eu
-rwxr-xr-x 1 root root 1129 Aug 28 10:15 /etc/apk/commit_hooks.d/rebuild-rtl8192eu*

HTPC

If there some high quality speakers lying around checkout the HTPC page.

Convert sys mode to diskless

  1. Set media name to save apkvol in the /etc/lbu/lbu.conf file LBU_MEDIA=mmcblk0p1 (Replace mmcblk0p1 with the correct partition name.)
  2. Configure /etc/fstab for Diskless Operation
    • Ensure the media partition (LBU_MEDIA) is listed with noauto.
    • Prevent it from being mounted read-write elsewhere.
    • Example: If the media was previously used for /boot. Update /etc/fstab:
      • UUID=E8C9-4979 /boot vfat noauto,ro,relatime,fmask=0022,dmask=0022,errors=remount-ro 0 2
      • UUID=E8C9-4979 /media/mmcblk0p1 vfat noauto,ro 0 0
    • Note: If /boot partition is FAT and was previously mounted automatically, no additional changes are needed for booting the diskless OS.
  3. Mount Root (/) Filesystem as Read-Only. Update /etc/fstab:
    • UUID=8e293498-41d6-4d3a-9832-fb4d3334dcc9 / ext4 ro,relatime 0 1
  4. Include Additional Files for Persistence outside of etc folder using lbu include command
  5. Commit Changes & Create apkvol using the command $doas lbu commit
  6. Reboot the pi into Diskless Mode .. $doas reboot

update packages.. or upgrade to newer release or update kernel..

  1. Remount (/) root and /boot(mandatory if kernel update is involved) as Read-Write

    • $doas mount -o remount,rw /
    • $doas mount -o remount,rw /boot
  2. Perform Updates finish updating of packages/kernel change or configure files

  3. Synchronize Disk Writes $doas sync

  4. Remount as Read-Only

    • $doas mount -o remount,ro /
    • $doas mount -o remount,ro /boot
  5. Commit Changes to apkvol $lbu commit

  6. Reboot $doas reboot

    For unbound following additional changes made Added roothints as per alpine linux wiki Update /etc/resolv.conf to use Unbound locally doas sh -c ’echo -e “nameserver 127.0.0.1\nsearch lan” > /etc/resolv.conf'

prabu@pizero2w ~> cat /etc/resolv.conf nameserver 127.0.0.1 search lan Prevent /etc/resolv.conf from being overwritten doas chattr +i /etc/resolv.conf doas chattr -i /etc/resolv.conf (to update later)

on the homepc2, the following changes made

Solution 1: Disable DHCP-provided DNS in iwd NameResolvingService=none # <— Change this from resolvconf to none Adjust resolvconf.conf (Preferred)

Edit /etc/resolvconf.conf to control what openresolv writes:

resolv_conf=/etc/resolv.conf name_servers=192.168.1.3 name_servers_only=yes # <— Add this line

The name_servers_only=yes directive ensures only your specified DNS servers (192.168.1.3) are listed.

Then, regenerate /etc/resolv.conf: $ doas resolvconf -u

In case of any issues in 192.168.1.3, just change the main.conf file and restart iwd: NameResolvingService=resolvconf # <— Change this from none to resolvconf prabu@homepc2 ~> doas service iwd restart prabu@homepc2 ~> cat /etc/resolv.conf

search lan nameserver 192.168.1.3 nameserver 192.168.1.1

Contributing to aports

Workflow summary

A Git fork is a personal copy of a repository. My fork lives on my GitLab account (prabuanand/aports.git) and serves as a central workspace for all my changes.

The initial forking of aports can be done on gitlab.alpinelinux.org to ones’ namespace.

For each new aport i want to contribute, i should create a new branch from my fork’s master branch. This keeps each contribution isolated and prevents conflicts.

Initial setup

The following one time setup must be done:

aports setup

git clone https://gitlab.alpinelinux.org/alpine/aports.git
cd aports

Change from https to git to complete setup for SSH access by changing your fork to use SSH. With SSH configured, one can push and pull without entering credentials.

git remote set-url origin git@gitlab.alpinelinux.org:prabuanand/aports.git 2>/dev/null || git remote add origin git@gitlab.alpinelinux.org:prabuanand/aports.git

The below command is an idempotent one-liner: “Update the upstream remote URL if it exists; otherwise, add it.”

git remote set-url upstream https://gitlab.alpinelinux.org/alpine/aports.git 2>/dev/null || git remote add upstream https://gitlab.alpinelinux.org/alpine/aports.git

Since i am not Alpine Linux developer, i don’t have write access to main Alpine repo. To prevent unwanted pushes i issue the below command:

git remote set-url --push upstream no_push

The outcome can be verified as follows:

prabu@homepc2 /d/m/p/l/a/aports (master)> git remote -v
origin	git@gitlab.alpinelinux.org:prabuanand/aports.git (fetch)
origin	git@gitlab.alpinelinux.org:prabuanand/aports.git (push)
upstream	https://gitlab.alpinelinux.org/alpine/aports.git (fetch)
upstream	no_push (push)

ssh setup

Check if you have SSH keys locally. If you don’t have keys, generate them first.

ls -la ~/.ssh/
ssh-keygen -t ed25519 -C "your.email@example.com"

Add key to SSH agent

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Copy public key and add it to your GitLab account from the website

cat ~/.ssh/id_ed25519.pub

Test the SSH Connection to gitlab.

ssh -T git@gitlab.alpinelinux.org
Welcome to GitLab, @prabuanand!

Steps for each contribution/update

Sync with master

For each new package update/contribution, make sure local and fork master are current by updating local master branch. This must be done before everytime a new branch is created.

git fetch upstream master
git checkout -B master upstream/master

If i want my fork’s master to look current on the GitLab web UI, use GitLab’s “Update fork” button. It’s a server-side sync, unaffected by local shallow depth.

Create a branch

Before creating a new branch, ensure no stale branch of the same name already exists on your fork:

git ls-remote origin <branch-name>

Create a feature branch from up-to-date master. A sample <branch-name> can be rtl8192eu-src, the same as aports package name.

git checkout -b <branch-name> master

Make the necessary changes like edit APKBUILD, bump pkgver, update checksums, etc.

abuild checksum
abuild -r

Now commit as per COMMITSTYLE.md document.

git add <path/to/APKBUILD>
git commit -m "testing/rtl8192eu-src: upgrade to 5.11.2.3_git20260629"

Push the changes to my fork.

git push origin <branch-name>

Open MR on GitLab website (link is printed after push), target alpine/aports:master

Now wait for the pipelines to finish. Wait for someone to approve the MR.

Monitor Package status

  • Watch for any build failures or issues
  • Check Alpine’s build logs at: https://build.alpinelinux.org/↗
  • Monitor package status in different Alpine versions (edge, v3.19, etc.)

Handling reviewer feedback

If a reviewer requests changes on the MR, edit and push updates on the same branch as GitLab updates the existing MR automatically. Do not create new branch or new MR.

git checkout <branch-name>

Make the requested change i.e edit APKBUILD as requested.

If the MR still has just your one original commit, amend it rather than stacking a new commit. This keeps history clean for a single-aport MR.

git add <path/to/APKBUILD>
git commit --amend --no-edit

Refresh the remote branch pointer first then Push. Here – force is required since amending rewrites the commit hash.

git fetch origin <branch-name>
git push --force-with-lease origin <branch-name>

Remember to reply to the reviewer’s comment on the MR (web UI) confirming it’s addressed and mark it as resolved. The pipeline re-runs automatically against the new commit.

Only use git rebase master (see Rebase MR below) if the MR is also flagged as behind/conflicting with upstream master.

Rebase MR

If master moved forward before MR merges then additional steps are required. This is very normal as others might have submitted MR’s or commits might have happened.

git fetch upstream master
git checkout -B master upstream/master

Rebasing one’s own single commit or branch onto a freshly reset master is safe here. It’s just our own branch’s history changing, not shared history.

Resolve any conflicts if git rebase below reports any. The –force option in the push command only writes to our own feature branch, not master.

git checkout <branch-name>
git rebase master
git fetch origin <branch-name>
git push --force-with-lease origin <branch-name>

Resolve conflicts

When git rebase master runs, git replays my commit(s) one at a time on top of the new master. A conflict happens when the same lines of a file were changed both by me and by someone else’s merged commit — git can’t automatically decide which version wins, so it pauses and asks.

After git rebase master stops on a conflict:

git status

This lists the conflicted file(s), e.g. testing/rtl8192eu-src/APKBUILD.

Open the file — git has inserted conflict markers:

<<<<<<< HEAD
pkgver=5.11.2.4
=======
pkgver=5.11.2.3_git20260701
>>>>>>> your-commit-message

    Everything between <<<<<<< HEAD and ======= is what's now on master (someone else's change).
    Everything between ======= and >>>>>>> is your change.

Edit the file by hand to keep the correct final version. Often we keep ours, but check. If the conflict is in shared boilerplate near my edit, i may need to keep master’s version and reapply my change around it. Remove the <<<<<<<, =====, >>>>>>> markers entirely — they’re not valid syntax, just leave clean file content.

Then continue rebase:

git add <path/to/APKBUILD>
git rebase --continue

If there are more commits queued in the rebase, git may pause again for the next conflict. Repeat the above steps. When done, we’re back at a clean state. Now fetch the remote branch and force-push, same as in Rebase MR above.

The force-push is required because rebase rewrote our commit(s) with new hashes and the remote branch has the old ones.

If you get stuck or want to bail out entirely:

git rebase --abort

This puts you back exactly where you were before starting the rebase. It’s always safe to use if you’re unsure.

For a single-package aport, conflicts are usually rare and small. If at all, it’s mostly just pkgver/pkgrel/checksum lines if someone else touched the same package, which is uncommon unless two people are working on the same aport simultaneously.

Cleanup after MR is merged

The final cleanup must be performed once the MR is merged by someone, so the fork is clean both locally and on the gitlab instance.

git push origin --delete <branch-name>
git fetch upstream master
git checkout -B master upstream/master
git branch -d <branch-name>

Things to avoid

  • Don’t push to origin/master at all. MRs diff against upstream/master directly, and your fork’s master is likely a protected branch that will reject force-pushes anyway. Use GitLab’s “Update fork” button if you want it to look current in the web UI.
  • Don’t shallow-clone this repo if you intend to push branches. Alpine’s GitLab rejects shallow-history pushes for any branch, not just master.

Remember to clean up any build artifacts

rm -rf ~/packages/testing/x86_64/rtl8192eu-src-*

Set Up Anitya Monitoring for Your Project

  1. Visit Anitya https://release-monitoring.org/↗
  2. Sign up for an account on Anitya if you don’t have one You can usually sign in with GitHub, GitLab, or other OAuth providers
  3. Add Your Project like below: Project Name: rtl8192eu-linux-driver (or similar) Homepage: https://github.com/Mange/rtl8192eu-linux-driver↗ Backend: GitHub (since it’s hosted on GitHub) Version URL: https://api.github.com/repos/Mange/rtl8192eu-linux-driver/releases↗
  4. After adding the project, create Alpine Linux Mapping to link the upstream project to your Alpine package Distribution: Alpine Linux Package name: rtl8192eu-dkms
  5. Set up how Anitya should detect new versions. GitHub Backend: Monitors GitHub releases/tags Version pattern: Configure if needed (e.g., if versions follow specific patterns) Filter: Set up filters to ignore pre-releases if desired
  6. After Setup Once the mapping is made, it should automatically send an email to the maintainer whenever a new version is available.

© Prabu Anand K 2020-2026