Alpine Linux with Full Disk Encryption
I recently built a file server. For the operating system, I decided to use Alpine Linux. As with most of my Linux systems, I wanted to utilize full disk encryption. The following guide is mostly based off the documentation on the Alpine Wiki, and goes through the installation of Alpine on a modern UEFI system, with LUKS full disk encryption.
- Michael Siegel has created a guide for Installing Alpine Linux with full disk encryption on BIOS/MBR systems with a custom partition layout.
- In the interest of helping people update other documentation, the text and code in this article (and only this article) are licensed under CC0.
After booting from the Alpine USB installation media, I used
ifconfig eth0 up and
udhcpc eth0 to bring up a network connection. I used
setup-apkrepos to configure the package manager. I then installed the following tools, needed for setting up encrypted disks and logical volumes.
apk update apk add gptfdisk cryptsetup lvm2 e2fsprogs util-linux dosfstools
Next I partitioned the disk. If your system is using an NVME device, the primary device will look like
/dev/nvme0n1 and the individual partitions will be
/dev/nvme0n1p2. For systems using SATA devices, your primary device will likely be
/dev/sda and the partitions will be
/dev/sda2. For the rest of this guide, I assumed an NVME device as the primary boot drive. If you’re using SATA devices, adjust the device names appropriately. To create a GPT partition, I used the
gdisk command installed from the
# gdisk /dev/nvme0n1 Command (? for help): p Disk /dev/nvme0n1: 1000215216 sectors, 476.9 GiB Model: SK hynix PC300 HFS512GD9MND-5510A Sector size (logical/physical): 512/512 bytes Disk identifier (GUID): 1C949023-866B-4764-B397-252D5125277C Partition table holds up to 128 entries Main partition table begins at sector 2 and ends at sector 33 First usable sector is 34, last usable sector is 1000215182 Partitions will be aligned on 2048-sector boundaries Total free space is 2014 sectors (1007.0 KiB) Number Start (sector) End (sector) Size Code Name 1 2048 104447 50.0 MiB EF00 EFI system partition 2 104448 1000215182 476.9 GiB 8300 Linux filesystem
I created a small EFI System Partition (ESP) of 50MB. This doesn’t need to be very large, as it will only hold the GRUB EFI executable used to boot the system (and possible other boot loaders as well if you choose to dual boot Windows or another operating system). Be careful not to make the partition too small or else you could get a
WARNING: Not enough clusters for a 32 bit FAT! error1 when attempting to format it.
Other than the GRUB boot loader, everything else on this system will be fully encrypted. GRUB2 allows for unlocking an LUKS encrypted system at boot, but only the LUKS 1 format. GRUB has added support for LUKS 22, but that support hasn’t made it into a stable release version yet. I used the following
cryptsetup commands to created the encrypted partition. To fully understand what these options mean, I suggest reading the Arch Wiki documentation on LUKS encryption modes and options. Learning what these options mean, and deciding which ones to use, is vital to ensuring the confidentiality of your system.
cryptsetup luksFormat --type luks1 -c aes-xts-plain64 -s 512 --iter-time 5000 --hash sha512 /dev/nvme0n1p2 cryptsetup luksOpen /dev/nvme0n1p2 enc_root
Next, I formatted my ESP partition as FAT32, and setup two logical volumes within the encrypted partition. One of the volumes was 16GB in size and formatted as swap, and the second filled the remaining space and was formated as ext4.
mkfs.fat -F32 /dev/nvme0n1p1 pvcreate /dev/mapper/enc_root vgcreate lvm /dev/mapper/enc_root lvcreate -L 16G lvm -n swap lvcreate -l 100%FREE lvm -n root mkswap /dev/lvm/swap mkfs.ext4 /dev/lvm/root
Next, I mounted the root and ESP file systems.
mount /dev/lvm/root /mnt -t ext4 mkdir /mnt/boot/efi -p mount /dev/nvme0n1p1 /mnt/boot/efi/
Take note of the UUID for the encrypted partition using the
blkid command. It will be needed for the GRUB configuration later.
blkid -s UUID -o value /dev/nvme0n1p2 /dev/nvme0n1p2: UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx" TYPE="crypto_LUKS"
I installed the Alpine base system using the
setup-disk command on the mounted volume.
setup-disk -m sys /mnt Installing system on /dev/lvm/root: /mnt/boot is device /dev/lvm/root 100% #########==> initramfs: creating /boot/initramfs-lts /boot is device /dev/lvm/root
To finish the install, I chrooted into the new Alpine environment. Before doing so, I needed to bind the virtual
dev file systems into the new environment.
mount -t proc /proc /mnt/proc mount --rbind /dev /mnt/dev mount --make-rslave /mnt/dev mount --rbind /sys /mnt/sys
Then I used
chroot on the environment, and changed my prompt as well to make it clear I was in the new root.
chroot /mnt source /etc/profile export PS1="(chroot) $PS1"
I needed to install
efibootmgr for my boot loader. Since I’m using UEFI and not BIOS/legacy, I removed
apk add grub-efi efibootmgr apk del syslinux
To get grub to work correctly with full disk encryption and my logical volumes, I needed to edit
/etc/default/grub, and add in the following options. Be sure to set the UUID to the one returned by
blkid above (the UUID of your LUKS encrypted partition).
rootfstypeargument for the root filesystem to the kernel command line below. The root filesystem type may need to be configured explicitly on some systems. If you don't, you may get the error message
"no such file or directory"when the initrd attempts to mount the root volume to
# /etc/default/grub GRUB_DISTRIBUTOR="Alpine" GRUB_TIMEOUT=2 GRUB_DISABLE_SUBMENU=y GRUB_DISABLE_RECOVERY=true GRUB_ENABLE_CRYPTODISK=y GRUB_CMDLINE_LINUX_DEFAULT="cryptroot=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx cryptdm=lvmcrypt cryptkey rootfstype=ext4" GRUB_PRELOAD_MODULES="luks cryptodisk part_gpt lvm"
The arguments in the
GRUB_CMDLINE_LINUX_DEFAULT listed above are not for the kernel but for the initial ram disk (initrd) created using
cryptroot argument specifies which device to unlock on boot and
cryptkey indicates that a key exists at
/crypto_keyfile.bin on the root file system that should be included in the initrd. The
cryptdm argument indicates the name of the encrypted disk mapping, which doesn’t really matter as the initrd will map the logical volume names for the rest of the boot process.
GRUB_PRELOAD_MODULES argument indicates which modules are baked into the GRUB unencrypted executable on the ESP. These modules allow GRUB to unlock an encrypted disk, read GPT partitions and load logical volumes, all of which are needed to boot this particular encrypted setup. A full list of modules can be found in
Next, I generated a random key and added it to my encrypted volume. The initial ram disk will be on the fully encrypted root volume, and GRUB will prompt me to unlock that volume on boot. Without adding this key to the LUKS volume and initrd, I would have to type in my password twice, once for GRUB to load the initrd and then the initrd would prompt me for it again.
dd bs=512 count=4 if=/dev/urandom of=/crypto_keyfile.bin chmod 000 /crypto_keyfile.bin cryptsetup luksAddKey /dev/nvme0n1p2 /crypto_keyfile.bin
I setup my initial ram disk. The
cryptkey features were added to
/etc/mkinitfs/mkinitfs.conf for unlocking the encrypted disk on boot. I’ve also added
nvme since I’ve booting from an NVME drive3, but it can be left out for systems with SATA system drives.
# /etc/mkinitfs/mkinitfs.conf features="ata base ide scsi usb virtio ext4 lvm cryptsetup cryptkey nvme"
Next, I made the initramfs and installed grub. I used
$(ls /lib/modules/) to specify the version of the kernel that is installed to the system disk, as it may be different from the kernel version used on the USB boot media. Some memory leak warnings may appear. These can be safely ignored.
mkinitfs -c /etc/mkinitfs/mkinitfs.conf $(ls /lib/modules/) grub-install --target=x86_64-efi --efi-directory=/boot/efi grub-mkconfig -o /boot/grub/grub.cfg
Before rebooting, I needed to run
passwd to set a password for the root user. Afterwards, I unmounted all the file systems and rebooted. If you get an error about
-R being an unrecognized option, it’s because you didn’t install
util-linux earlier on in this tutorial. The busybox
umount command doesn’t support recursive unmounting, which is needed for
passwd Changing password for root New password: Retype password: passwd: password for root changed by root exit cd / umount -R /mnt/proc /mnt/dev /mnt/sys /mnt/boot/efi umount /mnt reboot
Since I skipped the typical
setup-alpine installation method, and called
setup-disk to install the system partition, there are many parts of the system that did not get configured. At this point you can run
setup-alpine and skip the disk installation step, or call the individual setup tools such as
setup-ntp, and others. I do like how Alpine separates out each individual setup step into self-contained commands.
You should also setup swap by adding adding the line
/dev/mapper/lvm-swap none swap sw 0 0 to
/etc/fstab. You can also use the swap volume’s UUID instead of the lvm device node. After setting up swap, run
/etc/init.d/swap start to activate it and
rc-update add swap boot to ensure swap is activated when the system starts.
The Alpine initramfs isn’t very well documented. If you run into issues, the boot script for it can be located at
/usr/share/mkinitfs/initramfs-init. Reading through it is how I discovered where it expects the location for the
If GRUB fails to boot and you need to return to the installation USB to debug what’s happening, the command
vgchange -a y will load all the logical volumes once you’ve unlocked the encrypted LUKS volume. The
efibootmgr tool is also useful when trying to diagnose EFI boot order issues.
Sometimes it will take a few reboots to fully diagnose an issue, so I typically keep around all the commands I need to get me back into the chroot environment of the new install.
ifconfig eth0 up udhcpc eth0 setup-apkrepos -r apk update apk add gptfdisk cryptsetup lvm2 e2fsprogs util-linux cryptsetup luksOpen /dev/nvme0n1p2 enc_root vgchange -a y mount /dev/mapper/lvm-root /mnt mount /dev/nvme0n1p1 /mnt/boot/efi/ mount -t proc /proc /mnt/proc mount --rbind /dev /mnt/dev mount --make-rslave /mnt/dev mount --rbind /sys /mnt/sys chroot /mnt source /etc/profile export PS1="(chroot) $PS1"
Here are some common questions you should yourself in you run into issues:
efibootmgr -vshow the correct device and boot loader?
- Did GRUB not install correctly? (rerun
grub-installin the chroot environment)
- Did you forget to configure GRUB with
grub-mkconfig -o /boot/grub/grub.cfg?
- Does the initial ram disk have all the modules/features it needs? (Run
mkinitfs -Lfor a list)
- Is the ESP partition not formatted in a way the UEFI/BIOS can recognize it (did you remember to use
-F32? Maybe try FAT16 instead?)
Most Dell BIOS screens have a UI for configuring UEFI executables, which can be helpful for manually finding and loading the GRUB boot loader. Some motherboards have a UEFI diagnostic shell, but for the vast majority of systems I’ve seen, there are few if any diagnostic tools for UEFI and boot issues.
Installation with full disk encryption for Linux distributions such as Gentoo, Void and Alpine Linux, isn’t very straight forward. It typically involves going past the default installation instructions or setup tools, and requires knowledge about how Linux file systems and boot loaders are setup and configured. It does take some additional work, but having a core understanding of how Linux disk partitions, encryption, and boot loaders are setup, are valuable skills that carry over to other aspects of development, operations and Linux system administration.
WARNING: Not enough clusters for a 32 bit FAT! - SOLVED. Installation - Arch Linux Forums. Retrieved 13 October 2020. ↩
GNU GRUB - Bugs: bug #55093, Add LUKS2 support. 25 November 2018. dllud. GNU GRUB. Retrieved 14 October 2020. ↩
NVME - Alpine Linux. Alpine Linux wiki. Retrieved 13 October 2020. ↩