This document describes how to install Alpine Linux on BIOS/MBR systems with a fully encrypted hard drive (including /boot) and a custom partition layout. If you don't need a custom partition layout because Alpine's default (a swap partition and a single partition for everything else) suits you, you can simply use the setup-alpine script, which is way more convenient than what is described below.
This guide works for x86_64 machines and should work for 32-bit x86 machines (i686 and later) just as well.
The method chosen here is “LVM on LUKS”, basically meaning that we will be creating an encrypted container on the hard drive, set up logical volumes inside of it, and then install Alpine onto those logical volumes.
Note: While this document already provides a working solution, it is still a work in progress.
Warning: It is assumed that you're using a hard disk drive (magnetic platters spinning under a read/write head). The disk wiping operation shown below is not meant for SSDs because it will put unnecessary wear on such devices and likely not get rid of all existing data in the process.
Alpine installation images are available at https://alpinelinux.org/downloads. This guide uses the extended image because it already contains most of the software needed to install Alpine. This has the advantage that the package manager and networking can be set up later in the installation process, which is especially handy if the process needs to be interrupted at some point.
In addition to the image itself, also download the accompanying checksum file (*.sha256) and GPG signature (*.asc) into the same directory. Then use these files to verify the integrity and authenticity of the downloaded image.
On Linux and FreeBSD systems, verifying the checksum usually works as follows:
$ sha256sum -c alpine-extended-3.18.0-x86_64.iso.sha256 alpine-extended-3.18.0-x86_64.iso: OK
On NetBSD:
$ cksum -a sha256 -c alpine-extended-3.18.0-x86_64.iso.sha256 && echo OK OK
On OpenBSD:
$ sha256 -C alpine-extended-3.18.0-x86_64.iso.sha256 alpine-extended-3.18.0-x86_64.iso (SHA256) alpine-extended-3.18.0-x86_64.iso: OK
[TODO: Explain how to do this on MacOS and Windows.]
To verify the GPG signature, do the following:
$ gpg --verify alpine-extended-3.18.0-x86_64.iso.asc
For a proper signature, the output should look mostly like this:
gpg: assuming signed data in 'alpine-extended-3.18.0-x86_64.iso' gpg: Signature made Tue May 9 21:51:41 2023 CEST gpg: using RSA key 0482D84022F52DF1C4E7CD43293ACD0907D9495A gpg: Good signature from "Natanael Copa <ncopa@alpinelinux.org>" [unknown] gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 0482 D840 22F5 2DF1 C4E7 CD43 293A CD09 07D9 495A
If Alpine's public key is not already on your keyring, GPG won't be able to verify the signature:
$ gpg --verify alpine-extended-3.18.0-x86_64.iso.asc gpg: assuming signed data in 'alpine-extended-3.18.0-x86_64.iso' gpg: Signature made Tue May 9 21:51:41 2023 CEST gpg: using RSA key 0482D84022F52DF1C4E7CD43293ACD0907D9495A gpg: Can't check signature: No public key
To obtain the key, run gpg --recv-keys with the key ID in GPG's output:
$ gpg --recv-keys 0482D84022F52DF1C4E7CD43293ACD0907D9495A gpg: key 293ACD0907D9495A: public key "Natanael Copa <ncopa@alpinelinux.org>" imported gpg: Total number processed: 1 gpg: imported: 1
Then repeat the verification command.
In order to create a bootable installation medium, the image needs to be applied to some sort of storage device, e.g., a USB key or a DVD.
Warning: The following operation will destroy all data currently residing on the USB key you use for it!
Applying the image to a USB key can be done using the cat command on the image and redirecting its output to the device file representing the USB key, for example:
$ cat alpine-extended-3.18.0-x86_64.iso > /dev/sdX
Be sure to replace the name of the image with the name of the image you downloaded and double-check you have the correct device name.
On Linux and BSD systems, you can use cdrskin to write the installation image to a DVD via the command line.
For example, to burn the image onto a DVD at a speed of 4x and with verbose command output, run
cdrskin -v speed=4 image.iso
replacing image.iso with the file name of the image you want to burn to the DVD.
If there is only one optical disc drive on your system, it should be auto-detected. If there is more than one, find the device name using cdrskin --devices and then specify it with the dev parameter. For example, if your optical drive of choice is /dev/sr0, run
cdrskin -v dev=/dev/sr0 speed=4 image.iso
Warning: The following process will destroy all data currently residing on the hard drive you use for it!
Note: As this part of the installation does not need network access, you can keep the machine disconnected form the network for now.
Boot from the installation medium and log in as root. No password is required.
If you don't want to use the standard US keymap, run setup-keymap before doing anything else.
Install the required software:
# apk add cryptsetup e2fsprogs grub grub-bios lvm2 mkinitfs util-linux
If you prefer to use Btrfs instead of Ext4, replace e2fsprogs with btrfs-progs.
If you're not comfortable using the vi editor, you can also add Nano:
# apk add nano
To ensure that
the whole disk must be filled with random data before setting up encryption. (Cf. Wiping disk vs. initializing container.)
First, take a look at available disks, using the lsblk command and locate the one you want to install Alpine on. On the machine used for writing this guide, the output looks like this:
# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 7:0 0 157.4M 1 loop /.modloop sda 8:0 0 298.1G 0 disk └─sda1 8:1 0 298.1G 0 part sdb 8:16 1 14.5G 0 disk /media/sdb ├─sdb1 8:17 1 840M 0 part └─sdb2 8:18 1 1.4M 0 part sr0 11:0 1 1024M 0 rom
Here, /dev/sda represents the internal hard drive, /dev/sdb the installation medium. Thus, Alpine is going to be installed to /dev/sda.
Wiping the disk merely requires redirecting the content of /dev/urandom to the respective device file:
Warning: This operation will irrecoverably destroy all data on the target device! Double-checking for the correct device name is strongly advised.
# cat /dev/urandom > /dev/sdX
This operation will take a while, depending on your hardware. The machine I used to do the test installations for this guide took about one hour to fill its 300-gigabyte hard drive.
Note: If you need to, you can shut the computer down for now and continue later. You would only have to install the packages installed in 3.1 again (and maybe set your keymap before that, as in 3.) after booting from the installation medium next time.
Use fdisk to create a new DOS/MBR partition table and one partition spanning the whole disk:
# fdisk /dev/sda Welcome to fdisk (util-linux 2.38.1). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Command (m for help): o Created a new DOS disklabel with disk identifier 0xb68308e1. Command (m for help): n Partition type p primary (0 primary, 0 extended, 4 free) e extended (container for logical partitions) Select (default p): p Partition number (1-4, default 1): First sector (2048-625142447, default 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-625142447, default 625142447): Created a new partition 1 of type 'Linux' and of size 298.1 GiB. Command (m for help): w The partition table has been altered. Calling ioctl() to re-read partition table. Syncing disks.
Scan for new device nodes:
# mdev -s
Now set up the new partition as the encrypted device, using cryptsetup. Basically, like this:
# cryptsetup --verbose --verify-passphrase luksFormat --type luks1 /dev/sda1
We're using LUKS1 here because GRUB didn't support LUKS2 at the time of writing.
Here's a security-optimized variant of the above command:
# cryptsetup --verbose --verify-passphrase --cipher serpent-xts-plain64 --key-size 512 --hash sha512 --iter-time 5000 --use-random luksFormat --type luks1 /dev/sda1
[TODO: Explain options]
Short form of the above command:
# cryptsetup -vyc serpent-xts-plain64 -s 512 -h sha512 -i 5000 --use-random luksFormat --type luks1 /dev/sda1
After entering this command, cryptsetup will display a warning, ask you to confirm the operation, and request a passphrase to use:
WARNING! ======== This will overwrite data on /dev/sda1 irrevocably. Are you sure? (Type 'yes' in capital letters): YES Enter passphrase for /dev/sda1: Verify passphrase: Key slot 0 created. Command successful. #
Open the LUKS container:
# cryptsetup open /dev/sda1 vault
Note: The name vault is just an example. You can choose whatever you like. Only need to keep it consistent.
First, set up the LUKS container as the “physical volume” for LVM:
# pvcreate /dev/mapper/vault Physical volume "/dev/mapper/vault" successfully created. #
Create a new volume group:
# vgcreate vg0 /dev/mapper/vault Volume group "vg0" successfully created #
Note: The volume group may be named freely. A good option is to use the future hostname of your Alpine system instead of vg0.
Now you'll need to set up some logical volumes according to your desired partitioning scheme with lvcreate, e.g., like this:
# lvcreate -L 2G vg0 -n swap Logical volume "swap" created. # lvcreate -L 15G vg0 -n root Logical volume "root" created. # lvcreate -l 100%FREE vg0 -n home Logical volume "home" created. #
-l 100%FREE means: Use 100% of the remaining storage space for the volume group.
# mkswap -L alpine-swap /dev/vg0/swap # mkfs.ext4 -L alpine-root /dev/vg0/root # mkfs.ext4 -L alpine-home /dev/vg0/home
If you prefer to use Btrfs instead of Ext4, first run modprobe btrfs and then replace mkfs.ext4 with mkfs.btrfs.
Now you can activate the swap partition for use during the installation process:
# swapon /dev/vg0/swap
Note: If you need to, you can shut the computer down for now and do the actual installation later. You would only have to do the following after booting from the installation medium next time: install the required packages again (see 3.1), open the LUKS container (see 3.5), activate the LVM volumes by running lvchange -a y vg0, enable swap (see 3.6), and maybe set your keymap prior to doing any of that (see 3.).
Note: This section of the guide requires network access.
The latter sets up Busybox' device manager (mdev) just for bootstrapping (-C).
Simply run passwd without arguments.
Configure networking devices:
# setup-interfaces Available interfaces are: eth0 wlan0. Enter '?' for help on bridges, bonding and vlans. Which one do you want to initialize? (or '?' or 'done') [eth0] Ip address for eth0? (or 'dhcp', 'none', '?') [dhcp] Available interfaces are: wlan0. Enter '?' for help on bridges, bonding and vlans. Which one do you want to initialize? (or '?' or 'done') [wlan0] done Do you want to do any manual network configuration? (y/n) [n] #
Add the networking service to runlevel boot:
# rc-update add networking boot
# rc-update add seedrng boot * service seedrng added to runlevel boot # rc-update add crond default * service crond added to runlevel default # rc-update add acpid default * service acpid added to runlevel default
Start all services added to runlevels boot and default:
# openrc boot # openrc default
# setup-ntp Which NTP client to run? ('busybox', 'openntpd', 'chrony' or 'none') [chrony] 100% ████████████████████████████████████████████████████████████ * service chronyd added to runlevel default * Caching service dependencies ... ok * Starting chronyd ... ok #
# setup-apkrepos Available mirrors: 1) dl-cdn.alpinelinux.org 2) uk.alpinelinux.org 3) mirror.yandex.ru […] 60) mirror.2degrees.nz 61) mirror.kku.ac.th 62) mirror.uepg.br r) Add random from the above list f) Detect and add fastest mirror from above list e) Edit /etc/apk/repositories with text editor Enter mirror number (1-72) or URL to add (or r/f/e/done) [1] 5 Added mirror mirror1.hs-esslingen.de Updating repository indexes... done. #
setup-devd
I chose mdevd.
When asked whether you'd like to scan the hardware to populate /dev, simply go with the default answer: n. (We've already run this script.)
# setup-sshd
# mount /dev/vg0/root /mnt # mkdir -p /mnt/home # mount /dev/vg0/home /mnt/home
# setup-disk -m sys /mnt 100% ████████████████████████████████████████████████████████████ Installing system on /dev/mapper/vg0-root: /mnt/boot is device /dev/mapper/vg0-root […] ==> initramfs: creating /boot/initramfs-lts Generating grub configuration file ... Found linux image: /boot/vmlinuz-lts Found initrd image: /boot/initramfs-lts Warning: os-prober will not be executed to detect other bootable partitions. Systems on them will not be added to the GRUB boot configuration. Check GRUB_DISABLE_OS_PROBER documentation entry. done /boot is device /dev/mapper/vg0-root 0K You might need fix the MBR to be able to boot #
Mount essentials for chroot environment:
# mount -t proc /proc /mnt/proc # mount --rbind /dev /mnt/dev # mount --make-rslave /mnt/dev # mount --rbind /sys /mnt/sys
Change root to /mnt:
# chroot /mnt
Adjust the prompt to remind yourself you're in a chroot environment:
# export PS1="[chroot] $PS1"
Now you'll need to edit /etc/default/grub.
First, obtain the UUID of the partition hosting the LUKS container and append it to that file:
[chroot] / # blkid -s UUID -o value /dev/sda1 >> /etc/default/grub
Use this UUID as the value for cryptroot=UUID=uuid as shown below.
The file now needs to contain the following (only with a different UUID):
GRUB_TIMEOUT=2 GRUB_DISABLE_SUBMENU=y GRUB_DISABLE_RECOVERY=true GRUB_ENABLE_CRYPTODISK=y GRUB_CMDLINE_LINUX_DEFAULT="modules=sd-mod,usb-storage,ext4 cryptroot=UUID=283ca385-4c57-40f1-842b-6beda9c3daf1 cryptdm=vault cryptkey quiet rootfstype=ext4"
If you use Btrfs on your root partition, replace all instances of ext4 in GRUB_CMDLINE_LINUX_DEFAULT with btrfs.
The cryptkey parameter ensures that you won't have to enter the passphrase to unlock the LUKS container twice during boot.
As /boot will be encrypted on the installed system, the key to unlock the encrypted container must be included in the initial root file system used by the bootloader.
To create a key file to be picked up by mkinitfs later, run the following commands:
[chroot] / # dd bs=512 count=4 if=/dev/random of=/crypto_keyfile.bin 4+0 records in 4+0 records out [chroot] / # chmod 000 /crypto_keyfile.bin [chroot] / # cryptsetup luksAddKey /dev/sda1 /crypto_keyfile.bin Enter any existing passphrase: [chroot] / #
Note: The name and location of the key file must be exactly the same as in the above commands.
Look at /etc/mkinitfs/mkinitfs.conf:
[chroot] / # cat /etc/mkinitfs/mkinitfs.conf features="ata base ide scsi usb virtio ext4 lvm cryptsetup keymap"
Edit the file: Add cryptkey to features=.
Generate initfs:
mkinitfs -c /etc/mkinitfs/mkinitfs.conf kernel_version
Make sure you specify the kernel version of the system that is going to be installed to disk. It might be different from the one used by the installation medium. When in doubt, just use the directory with the latest kernel version in its name from /lib/modules.
[chroot] / # ls /lib/modules 6.1.30-0-lts 6.1.30-1-lts 6.1.31-0-lts [chroot] / # mkinitfs -c /etc/mkinitfs/mkinitfs.conf 6.1.31-0-lts ==> initramfs: creating /boot/initramfs-lts
[chroot] / # grub-install /dev/sda Installing for i386-pc platform. Installation finished. No error reported.
[chroot] / # grub-mkconfig -o /boot/grub/grub.cfg Generating grub configuration file ... Found linux image: /boot/vmlinuz-lts Found initrd image: /boot/initramfs-lts Warning: os-prober will not be executed to detect other bootable partitions. Systems on them will not be added to the GRUB boot configuration. Check GRUB_DISABLE_OS_PROBER documentation entry. done
In order to make the system activate the drive's swap partition automatically while booting, you'll have to put an entry for that partition into /etc/fstab and add the swap service to runlevel boot.
Add swap partition entry to /etc/fstab:
[chroot] / # printf 'UUID=%s swap swap defaults 0 0\n' "$(blkid -s UUID -o value /dev/vg0/swap)" >> /etc/fstab
Add swap service to runlevel boot:
[chroot] / # rc-update add swap boot * service swap added to runlevel boot
[chroot] / # setup-user -a -g 'audio video netdev' Setup a user? (enter a lower-case loginname, or 'no') [no] alf Full name for user alf [alf] Changing password for alf New password: Retype password: passwd: password for alf changed by root Enter ssh key or URL for alf (or 'none') [none] (1/1) Installing doas (6.8.2-r4) 100% ███████████████████████████████████████████████████████████ 0K Executing busybox-1.36.0-r9.trigger OK: 45 MiB in 94 packages #
Option -a here adds the user to the wheel group, which is required for using the doas command. Then, -g 'audio video netdev' adds the user to those groups as well.
If the machine you're installing Alpine on has an optical drive, you'll also have to add yourself to the cdrom group in order to access that drive without superuser privileges later. Beware though, that this won't enable you to mount an optical disc as a normal user. But it allows for playing audio or video discs and using the eject command, for example.
Finally, reboot:
[chroot] / # reboot
Install some reasonable TTY font, e.g.:
$ doas apk add font-terminus
Installed TTY fonts are located in /usr/share/consolefonts.
Use setfont to test fonts, e.g.:
$ setfont /usr/share/consolefonts/ter-224n.psf.gz
To make this a permanent setting, add consolefont="ter-224n.psf.gz" to /etc/conf.d/consolefont and comment out the default setting.
To have this font setting loaded during boot, add the consolefont service to runlevel boot:
$ doas rc-update add consolefont boot * service consolefont added to runlevel boot
Alpine Linux mirrors generally offer three kinds of repositories: main, community, and testing. The main repository includes only packages that are officially supported by Alpine. The community repository provides additional packages that have been tested to work on Alpine. Finally, testing, as the name suggests, is for packages that have issues or need testing. This repository is thus only available on the edge branch of Alpine, not on its stable releases.
After installation, the repository list in /etc/apk/repositories already contains a line for the community repository that you just need to uncomment by removing the leading hash sign (#) to enable APK to make use of it. Using this repository in addition to main provides a much wider variety of software. To name just three examples, community includes the MPV media player, the w3m web browser, and the HexChat IRC client.
In order to be able to find software in the community repository and install it, APK needs to re-read the repository list and get the respective index file. This is easily achieved by running doas apk update.
[TODO] Introduce basic APK commands.
One of the first things you'll probably want to add to the newly installed system is documentation. The reason this step is necessary is, first, that Alpine splits off most software documentation (including manual pages) into sub-packages with the -doc suffix (e.g., bash-doc for bash) in order to achieve a higher package granularity and keep the main packages small. Second, the system's core manual pages (mostly documenting the kernel and C library interfaces) reside in the man-pages package, which is not installed by default. Third, some tools needed to make use of manual pages are not installed by default either.
To obtain documentation and the tools to use it, install the following packages:
$ doas apk add docs mandoc mandoc-apropos man-pages
What these are for:
Now, what software to install on top of that depends very much on personal needs and preferences. What follows are therefore mere suggestions to give you an idea of what's available and might be useful:
To explore other available software, you can use APK's search, list and info commands. Run man apk for more information.
The sound setup on Linux commonly involves PipeWire these days and Alpine can be set up to use it as well. For a simple setup, however, installing alsa-lib and alsa-utils and then adjusting the levels and unmuting Master in alsamixer is sufficient.
[TODO] Document PipeWire setup.
There's more than one way to do it. This is one way to do it.
setxkbmap -layout de & xrdb -load ~/.Xresources & xsetroot -solid "#2F4F4F" & exec openbox-session
Finally, run startx.
You'll probably want to install some additional software, such as a graphical e-mail or IRC client, a media player, etc.
[TODO] Document setup-desktop. Elaborate on “manual” setup. Document Wayland setup.
There are several ways to set up WiFi on Alpine. This section presents a rather traditional method, using wireless-tools, wpa_supplicant, and the /etc/network/interfaces file.
First, let's use apk add to install the required software, namely:
Installing the linux-firmware package will most likely pull in the required drivers for your WiFi device. However, linux-firmware being a meta-package, it will simply install all Linux firmware packages that are available on Alpine. That might not be what you want. For example, the machine used for the test installations for this guide merely required the linux-firmware-i915 package to make use of its Intel WiFi adapter. So, in this case, the apk command looked like this:
$ doas apk add wireless-tools wpa_supplicant linux-firmware-i915
In order to know which firmware package is needed, first find out about the kind of WiFi adapter on your machine. Filtering the output of lspci or lsusb for things like "wifi", "wireless", "wlan", "network", or "ethernet" with grep -i should help accomplish that. Then find the appropriate firmware package for the device.
Next, list available WiFi interfaces by running iwconfig:
$ iwconfig lo no wireless extensions. eth0 no wireless extensions. wlan0 IEEE 802.11 ESSID:off/any Mode:Managed Access Point: Not-Associated Tx-Power=off Retry short limit:7 RTS thr:off Fragment thr:off Power Management:off
Scan for WiFi networks to confirm the AP is there (unless it's hidden), or if you don't remember the SSID exactly:
$ iwlist scan
Or, using a pager:
$ iwlist scan | less
Now use wpa_passphrase to create an appropriate network stanza in /etc/wpa_supplicant/wpa_supplicant.conf. Because you need to redirect output to a file only root can write to here, you'll have to use su to get a root shell for this part.
$ su Password: # wpa_passphrase 'essid' >> /etc/wpa_supplicant/wpa_supplicant.conf # reading passphrase from stdin <Type password here. Then hit Enter.> # exit $
The result in the file should look like this, except for the values:
network={ ssid="NotMyWLAN" #psk="notmypassword" psk=885c5d90dbb5888193aab23e9fa667a9468a3d190a01903a8b82d76e5f9460c2 }
By the way, make sure access to /etc/wpa_supplicant/wpa_supplicant.conf is thoroughly restricted. No one except root should be able to even read that file. This is easily achieved by running
$ doas chmod 000 /etc/wpa_supplicant/wpa_supplicant.conf
Also, when WPA access has been tested to work, the line containing the actual WPA password is better removed.
Test WPA access:
$ doas wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
If it works, start wpa_supplicant in the background:
$ doas wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
Now let wlan0 acquire an IP address on the local network:
$ doas udhcpc -i wlan0 udhcpc: started, v1.36.1 udhcpc: broadcasting discover udhcpc: broadcasting discover udhcpc: broadcasting select for 192.168.0.3, server 192.168.0.1 udhcpc: lease of 192.168.0.3 obtained from 192.168.0.1, lease time 3600
Test network access by, e.g., visiting Alpine's website in your prefered browser.
To set up the wlan0 interface permanently, add the following to /etc/network/interfaces:
auto wlan0 iface wlan0 inet dhcp wifi-config-path /etc/wpa_supplicant/wpa_supplicant.conf
If you don't want the system to try to bring up the WiFi interface during boot, comment the line saying auto wlan0, i.e., make it #auto wlan0. I highly recommend doing that. You can bring the interface up with a simple doas ifup wlan0 as needed. This also goes for the Ethernet interface, of course.
Now stop wpa_supplicant if it's still running. Then run doas ifdown wlan0 && doas ifup wlan0 to test the configuration in /etc/network/interfaces. If it works, WiFi setup is done.
[TODO] Elaborate on why commenting auto lines is recommended.
[TODO] Explain how to set up automatic reconnecting on flaky connections.
[TODO] Explain how to connect to open WiFi networks.
Much of the information presented here was extracted from the Alpine Linux Wiki and sorted out and brushed up with the help of several people on the #alpine-linux IRC channel at OFTC that are far more knowledgeable about Alpine Linux (and Linux in general) than me. I also took a lot of inspiration from a similar guide published by Sumit Khanna in 2020.
Last changed: 2023-07-30
Copyright © 2023 Michael Siegel
This work is made available under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0).