Alpine Linux on Raspberry Pi 4: Headless Installation & Persistent Storage

The ultimate guide, from creating the bootable MicroSD, to setting up persistent storage.

image

This guide shows how to perform a Headless Installation of Alpine Linux 3.14 64bit on a Raspberry Pi 4 B with Persistent Storage, using a Linux Workstation.

The term Headless Installation means that the Raspberry Pi will be only accessed via Network (ssh) since the first boot, and Persistent Storage means that will be set up a second partition to persistently hold the user files and the data generated by the services the Raspberry Pi may serve.

The system will be installed as Diskless Mode and any changes to the system will be saved using the Alpine Local Backup.

You don’t need to be a experienced Linux user to follow this guide! It is just required that you are a curious person with basic command-line interface skills such as, to execute commands with sudo privileges and editing text files.

Requirements:

  • Raspberry Pi 4B

  • A 15.3W USB-C power supply (official or similar).

  • A Class 10 MicroSD card of 16GB or more.

  • A computer running Linux and sudo privileges.

  • Cable or WiFi internet connection allowing the Raspberry Pi and the Linux computer to be connected to the same network.


Create a Bootable MicroSD Card With Two Partitions

The goal is to have a MicroSD card containing two partitions:

The system partition: A fat32 partition, with boot and lba flags, on a small part of the MicroSD card, enough to store the system and the applications (suggested 256MB to 2GB).

The storage partition: A ext4 partition occupying the rest of the MicroSD card capacity, to use as persistent storage for the users and for the data generated by the services the Raspberry Pi may serve.

Identify the MicroSD card

  • Do not plug it into your computer and run the command:

    lsblk

  • Take note of the output, plug the MicroSD card into your computer and run lsblk again. Sample output:

    NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
    sda      8:0    0 223,6G  0 disk 
    ├─sda1   8:1    0   600M  0 part /boot/efi
    ├─sda2   8:2    0     1G  0 part /boot
    └─sda3   8:3    0   222G  0 part /home
    sdb      8:16   0 465,8G  0 disk 
    └─sdb1   8:17   0 465,8G  0 part 
    sdc      8:32   0 465,8G  0 disk 
    └─sdc1   8:33   0 465,8G  0 part 
    sdd      8:48   1  59,5G  0 disk 
    └─sdd1   8:49   1  59,5G  0 part /run/media/rossijonas/67E7-60D4
    zram0  252:0    0     4G  0 disk [SWAP]
    
  • Notice that sdd is the 64gb MicroSD card (in this case), and it has one partion sdd1 mounted.

    From now on, you may copy/paste the commands, but remember to replace sdd with the MicroSD disk name identified on your machine.

Cleanup

  • Unmount the partition(s):

    sudo umount /dev/sdd1

    or

    sudo umount /dev/sdd?* (If the device has more than one partition mounted.)

  • Remove the partition(s):

    sudo parted /dev/sdd rm 1

    (Repeat that for other existent partitions if needed, replacing 1 with the partition number.)

  • Erase filesystem/partition-table signatures:

    sudo wipefs -af /dev/sdd

  • Verify that the device has no label applied and an empty partition table:

    sudo parted /dev/sdd print

    Sample output:

    Error: /dev/sdd: unrecognised disk label
    Model: Generic STORAGE DEVICE (scsi)      
    Disk /dev/sdd: 63,9GB
    Sector size (logical/physical): 512B/512B
    Partition Table: unknown
    Disk Flags: 
    
    

Create the Desired Partitions

  • Create MBR partitioning scheme on the MicroSD card:

    sudo parted /dev/sdd mklabel msdos

    (You may run sudo parted /dev/sdd print anytime to observe the changes applied.)

  • Create the system partition (2GB):

    sudo parted /dev/sdd mkpart primary 1 2g

  • Add boot and lba flags to the system’s partition:

    sudo parted /dev/sdd set 1 boot on

    sudo parted /dev/sdd set 1 lba on

  • Create the storage partition (62GB):

    sudo parted /dev/sdd mkpart primary 2001 64g

    (Replace 64g with the size of your MicroSD card, ex.: 16g.)

  • Build a fat32 filesystem on partition 1 and an ext4 filesystem on partition 2:

    sudo mkfs.vfat -F 32 /dev/sdd1

    sudo mkfs.ext4 /dev/sdd2

  • Verify the MicroSD card’s state with sudo parted /dev/sdd print again, it should end up with a scheme like this:

    Model: Generic STORAGE DEVICE (scsi)
    Disk /dev/sdd: 63,9GB
    Sector size (logical/physical): 512B/512B
    Partition Table: msdos
    Disk Flags: 
      
    Number  Start   End     Size    Type     File system  Flags    
     1      1049kB  2000MB  1999MB  primary  fat32        boot, lba
     2      2001MB  63,9GB  61,9GB  primary  ext4                  
    

Flash Alpine Linux Into the MicroSD Card

  • Download Alpine Linux for Raspberry Pi (aarch64 for Raspberry Pi 4 B).

  • Eject and remove the MicroSD, and then plug it on again.

  • Run lsblk to verify both partition mount points.

    Sample output:

    sdd      8:48   1  59,5G  0 disk 
    ├─sdd1   8:49   1   476M  0 part /run/media/rossijonas/4F85-22E7
    └─sdd2   8:50   1  59,1G  0 part /run/media/rossijonas/6a9f9b14-628b-431f-9f3c-721d526
    
  • Extract the tarball into the system’s (bootable fat32 2GB) partition:

    cd /run/media/rossijonas/4F85-22E7

    tar -zxvf ~/Downloads/alpine-rpi-3.14.0-aarch64.tar.gz

    (Substitute directories and filenames.)

  • Since it will be a headless install (without an external monitor plugged in) you can setup minimum memory usage for the GPU, maximizing available memory, via a user custom configuration file:

    (Make sure you’re still on the same working directory as the previous command. Check with pwd.)

    echo "gpu_mem=32" > usercfg.txt


Set Up for the Headless Installation

To perform a Headless Installation it is required to have an overlay file on the root of the bootable partition, containing some predefined networking configuration that will allow the Raspberry Pi to connect and to be accessible on the local network.

Verify the Local Network Settings, and Identify an Available IP Number

  • Install nmap from the package manager:

    RHEL/CentOS/Fedora/RockyLinux - sudo dnf install nmap

    Ubuntu/Mint/Kali/Debian(or other Debian-based) - sudo apt install nmap

    …or from any of the alternative ways.

  • Verify the IP address of your computer on the subnet it is connected:

    hostname -I

    Sample output:

    192.168.0.129 192.168.122.1 10.19.0.12 100.85.0.1 fdeb:446c:912d:8da:: fdeb:446c:912d:8da:: 
    

    What you need is the first IP from the output 192.168.0.129 (on this example), and from that, assuming the computer is connected with standard configuration, the gateway IP address will be 192.168.0.1.

  • Scan the local subnet, to identify IPs currently in use:

    nmap -sn 192.168.0.0/24

    Sample output:

    Starting Nmap 7.80 ( https://nmap.org ) at 2021-06-22 10:01 -03
    Nmap scan report for _gateway (192.168.0.1)
    Host is up (0.00036s latency).
    Nmap scan report for 192.168.0.128
    Host is up (0.0053s latency).
    Nmap scan report for fd2lm (192.168.0.129)
    Host is up (0.000038s latency).
    Nmap scan report for 192.168.0.145
    Host is up (0.0053s latency).
    Nmap done: 256 IP addresses (4 hosts up) scanned in 3.26 seconds
    

    Verify what IPs are in use and must not be reassigned. On the example, it is the IPs ending in .128, .129, and .145.

Configure, Build and Add the Overlay File Into the Bootable Partition

It could be a bit tricky to manually set up the directories/files/scripts needed to build the overlay file from scratch (and it could be another blog post) so, to simplify, we are going to use a ‘boilerplate’ solution and assign the required configuration on top of it.

  • Download this repository https://github.com/mesca/alpine_headless.

    (This is a crafted solution to simplify the creation of an overlay file that temporarily sets up the network connection and the SSH server for the Alpine’s first boot on the Raspberry Pi.)

  • Apply the network configurations:

    With the repository on your machine, edit the following file on your code editor:

    alpine_headless/ovl/etc/network/interfaces
    

    Replace the gateway address with your subnet gateway address (192.168.0.1 in this example), and assign a static IP address by replacing the address with an available IP address as previously verified. I will use the ending .125 for this example.

    The file will look similar to this:

    auto lo
    iface lo inet loopback
    
    auto eth0
    #iface eth0 inet dhcp
    iface eth0 inet static
        address 192.168.0.125
        netmask 255.255.255.0
        gateway 192.168.0.1
    
    auto wlan0
    iface wlan0 inet dhcp
    
  • Setup the WiFi connection:

    (You may skip this step if the Raspberry Pi will connect via network cable.)

    Edit the following file on your code editor:

    alpine_headless/ovl/etc/wpa_supplicant/wpa_supplicant.conf
    

    Change the local WiFi network name (ssid) and the password (psk), example:

    network={
        ssid="change_me"
        psk="change_me"
    }
    
  • Build the overlay file:

    Make sure you are at the repository’s alpine_headless/ folder, and run:

    ./make.sh

  • Copy the overlay file to the root of the MicroSD boot partition:

    cp localhost.apkovl.tar.gz /run/media/rossijonas/4F85-22E7

    (Replace the MicroSD mount point. Check again with ‘lsblk’ if needed.)

  • Double check the MicroSD boot partition, it must contain the Alpine Linux files, the usercfg.txt file and the localhost.apkovl.tar.gz file:

    ls -la /run/media/rossijonas/4F85-22E7

    Output should look similar to this:

    total 5548
    drwxr-xr-x. 5 rossijonas rossijonas    4096 dez 31  1969 .
    drwxr-x---+ 4 root       root            80 jun 22 11:09 ..
    drwxr-xr-x. 3 rossijonas rossijonas    4096 jun 15 11:35 apks
    drwxr-xr-x. 2 rossijonas rossijonas    4096 jun 15 11:35 boot
    drwxr-xr-x. 2 rossijonas rossijonas   20480 jun 15 11:35 overlays
    -rw-r--r--. 1 rossijonas rossijonas      25 jun 15 11:35 .alpine-release
    -rw-r--r--. 1 rossijonas rossijonas   26894 jun 11 05:10 bcm2710-rpi-2-b.dtb
    -rw-r--r--. 1 rossijonas rossijonas   28392 jun 11 05:10 bcm2710-rpi-3-b.dtb
    -rw-r--r--. 1 rossijonas rossijonas   29011 jun 11 05:10 bcm2710-rpi-3-b-plus.dtb
    -rw-r--r--. 1 rossijonas rossijonas   26890 jun 11 05:10 bcm2710-rpi-cm3.dtb
    -rw-r--r--. 1 rossijonas rossijonas   49214 jun 11 05:10 bcm2711-rpi-400.dtb
    -rw-r--r--. 1 rossijonas rossijonas   49218 jun 11 05:10 bcm2711-rpi-4-b.dtb
    -rw-r--r--. 1 rossijonas rossijonas   49892 jun 11 05:10 bcm2711-rpi-cm4.dtb
    -rw-r--r--. 1 rossijonas rossijonas   20120 jun 11 05:10 bcm2837-rpi-3-a-plus.dtb
    -rw-r--r--. 1 rossijonas rossijonas   20525 jun 11 05:10 bcm2837-rpi-3-b.dtb
    -rw-r--r--. 1 rossijonas rossijonas   20989 jun 11 05:10 bcm2837-rpi-3-b-plus.dtb
    -rw-r--r--. 1 rossijonas rossijonas   19852 jun 11 05:10 bcm2837-rpi-cm3-io3.dtb
    -rw-r--r--. 1 rossijonas rossijonas   52456 jun 15 06:06 bootcode.bin
    -rw-r--r--. 1 rossijonas rossijonas      60 jun 15 11:34 cmdline.txt
    -rw-r--r--. 1 rossijonas rossijonas     408 jun 15 11:34 config.txt
    -rw-r--r--. 1 rossijonas rossijonas    5448 jun 15 06:06 fixup4.dat
    -rw-r--r--. 1 rossijonas rossijonas    7314 jun 15 06:06 fixup.dat
    -rw-r--r--. 1 rossijonas rossijonas     927 jun 22 11:10 localhost.apkovl.tar.gz
    -rw-r--r--. 1 rossijonas rossijonas 2229120 jun 15 06:06 start4.elf
    -rw-r--r--. 1 rossijonas rossijonas 2953312 jun 15 06:06 start.elf
    -rw-r--r--. 1 rossijonas rossijonas      11 jun 21 12:11 usercfg.txt
    
  • Eject and remove the MicroSD card, it’s finally ready for the first boot!


Headless Installation, Setup & Persistent Storage

Remote Access to the Raspberry Pi

  • Insert the MicroSD card into the Raspberry Pi, plug the network cable (if needed), and power on.

  • After a few seconds, run nmap -sn 192.168.0.0/24 again and verify that the Raspberry Pi is connected with the IP address previously defined.

  • Remotely access the Raspberry Pi via SSH as root user with no password:

    ssh root@192.168.0.125

    (Remember to replace the IP address from this example.)

    If you get an error like this:

    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
    (...)
    

    That means that there was already another system using the static IP assigned for the Raspberry Pi, and there is a fingerprint on your local machine associated with this remote.

    If that error happens, edit the file ~/.ssh/known_hosts removing the line that starts with the IP address assigned to the Raspberry Pi.

    If the SSH access was successful, you will see a prompt like this:

    Welcome to Alpine!
    
    The Alpine Wiki contains a large amount of how-to guides and general
    information about administrating Alpine systems.
    See <http://wiki.alpinelinux.org/>.
    
    You can setup the system with the command: setup-alpine
    
    You may change this message by editing /etc/motd.
    
    (none):~# 
    

    (Notice that the prompt line ending with # shows that you are logged as root user.)

Alpine Linux Initial Setup

  • Perform Alpine’s initial setup:

    setup-alpine

    You will be prompted to assign/select:

    • Keyboard layout
    • Hostname (You may leave empty, use default)
    • Network Interface (You may leave empty, use default)
    • IP Address (You may leave empty to keep using current IP address assigned)
    • Netmask (You may leave empty, use default)
    • Gateway (You may leave empty, use default)
    • Network Manual Configuration (You may leave empty, use default)
    • Gateway (You may leave empty, use default)
    • DNS Domain Name (You may leave empty, use default)
    • DNS Domain Server (You may leave empty, use default)
    • Change Root Password (You must create the root password)
    • Timezone
    • HTTP/FTP Proxy (You may leave empty)
    • NTP client (You may leave empty, use default)
    • Repository Mirror (You may leave empty, use default)
    • SSH Server (You may leave empty, use default)
    • “Enter where to store configurations” (Assign mmcblk0p1 if not selected by default.)
    • Cache Dir (Assign /media/mmcblk0p1/cache if not selected by default.)
  • Update the system after setup:

    apk update && apk upgrade

  • Save all the changes applied by setup-alpine and the system update:

    By default, Alpine Linux is installed as Diskless Mode, and any changes might be saved using Alpine Local Backup (lbu).

    lbu commit -d

  • Add basic configuration to the new SSH server installed:

    The overlay file created previously won’t run anymore since the system was installed and set up via the setup-alpine script. That means the SSH server we accessed on the first boot will not be set anymore.

    Before the first reboot, the SSH server installed by the setup-alpine script must be configured to allow remote access again.

    Open the following file on the text editor of your choice (vi/nano):

    (Alpine Linux uses vi as the default text editor, If you are not comfortable with vi/vim, I recommend you to install nano to perform this task: apk add nano.)

    vi /etc/ssh/ssd_config or nano /etc/ssh/sshd_config

    Find the following directives, uncomment and assign the values like the example below, then save and quit the editor:

    (...)
    Port 22
    (...)
    PermitRootLogin yes
    (...)
    PubkeyAuthentication yes
    (...)
    PasswordAuthentication yes
    PermitEmptyPasswords no
    (...)
    
  • Commit the changes and reboot:

    lbu commit -d

    reboot

  • After reboot, access the Raspberry Pi as root again: ssh root@192.168.0.125. This time you will be prompted for the root password created.

Set Up the Persistent Storage

Set up the second MicroSD partition to be used as the home directory for the user that will be created and also to serve as persistent storage.

  • Verify the presence of both partitions with the command:

    blkid

    Sample output:

    /dev/mmcblk0p2: UUID="8a98a5df-e8a9-4f03-b1f6-6c9ecad2e854" TYPE="ext4"
    /dev/mmcblk0p1: UUID="0815-9310" TYPE="vfat"
    
  • Create a mount point for the second partition (mmcblk0p2):

    mkdir /media/mmcblk0p2

  • Add the mount instruction to fstab (and mount all again):

echo "/dev/mmcblk0p2 /media/mmcblk0p2 ext4 rw,relatime 0 0" >> /etc/fstab

mount -a

(You may double-check that it is mounted with mount.)

  • Commit the changes:

    lbu commit -d

Create an Admin User

  • Create the user’s (user1) home directory on the persistent storage:

    mkdir -p /media/mmcblk0p2/home/user1

  • Create the admin user (user1):

    adduser -h /media/mmcblk0p2/home/user1 user1

  • Add user to wheel group:

    adduser user1 wheel

  • Install sudo:

    apk add sudo

  • Allow users from wheel group full access to the system, by applying sudo command and providing the user password:

    visudo

    This command will open the /etc/sudoers file on the vi text editor. You must find and uncomment the following line:

    # %wheel ALL=(ALL) ALL

    Instructions for who never used the vi/vim editor:

    • Navigate down to find the line we want, by using the arrow keys.
    • When the line is found, place the cursor on top of the # character and type x key.
    • Type :x and press Enter to save and leave.
  • Commit the changes again:

    lbu commit -d

  • Update the SSH server configuration to block root login attempts:

    (From now on the system will be accessed remotely by the admin user created user1.)

    Open the following file on the text editor of your choice (vi/nano):

    vi /etc/ssh/ssd_config or nano /etc/ssh/sshd_config

    Find the following directive, assign the value, save and quit the editor:

    (...)
    PermitRootLogin no
    (...)
    
  • Commit and reboot:

    lbu commit -d

    reboot

  • After reboot, you are able to connect via SSH as the admin user created:

    ssh user1@192.168.0.125

    Issue the command whoami to verify the current user name and the command pwd to verify the home directory.

    Sample Output:

    Welcome to Alpine!
    
    The Alpine Wiki contains a large amount of how-to guides and general
    information about administrating Alpine systems.
    See <http://wiki.alpinelinux.org/>.
    
    You can setup the system with the command: setup-alpine
    
    You may change this message by editing /etc/motd.
    
    rpi4b-s1:/$ whoami
    user1
    rpi4b-s1:/$ pwd
    /media/mmcblk0p2/home/user1
    

    Verify that there’s the $ sign at the end of the user command prompt, which means you are logged as a regular user.


Post Installation

The Alpine installation is ready and the Raspberry Pi is good to go! In this section, I will recommend some tweaks you may find useful.

Before the tweaks I would like to reinforce the storage strategy in place:

Any changes to the system including configuration and packages installations must be saved manually using Alpine Local Backup (lbu commit -d), otherwise, it won’t survive a system reboot.

Any user files and user configurations stored under user’s home directory will normally persist a system reboot, as well as any other files stored at the second partition, mounted on /media/mmcblk0p2.

  • Install man pages. man is your best friend and Alpine Linux doesn’t provide it by default:

    sudo apk add mandoc man-pages mandoc-apropos less less-doc

  • Enable edge repositories to be able to install a variety of packages.

    Open the /etc/apk/repositories on the text editor:

    sudo vi /etc/apk/repositories

    Uncomment the entries for community and main edge repositories:

    /media/mmcblk0p1/apks
    http://dl-cdn.alpinelinux.org/alpine/v3.14/main
    #http://dl-cdn.alpinelinux.org/alpine/v3.14/community
    http://dl-cdn.alpinelinux.org/alpine/edge/main
    http://dl-cdn.alpinelinux.org/alpine/edge/community
    #http://dl-cdn.alpinelinux.org/alpine/edge/testing
    

    Save and quit the text editor.

  • Install lm-sensors to quick check the Raspberry Pi CPU temperature in a human-readable form:

    sudo apk add lm-sensors

    Check CPU temperature with the command: sensors.

  • Commit the post-installation changes:

    lbu commit -d.

  • Finally, extra step, show off!

    • Install neofetch: sudo apk add neofetch.
    • Run neofetch command! It will print the system information on the terminal, just like the header image from this post.
    • Print or take a picture of the screen.
    • Tweet the picture saying: “Thanks @rossijonas”

  • Alpine Linux Wiki
  • Raspberry Pi Documentation
  • “Mastering Vim Quickly” Book (and screencasts) by Jovica Ilic. In this guide you might noticed that vi/vim are very common tools on the Linux ecosystem. If you would like to gain or improve Vim skills very fast, that is the book I recommend. Make sure you subscribe to the newsletter to get free Vim tips and book excerpts on your email.

(👆 This is an affiliate link. If you got any value from this post and are also interested in buying the MVQ book/screencasts, please consider visiting the affiliate link and you’ll be gifting me a coffee ☕ or a beer 🍺.)


Footnotes:

  • Follow me on Twitter to get more posts like this and other quick tips in your feed.
  • If you have any doubts or tips about this article, I’d appreciate knowing and discussing it via email.
  • Do you have any other Linux tips? Would you like to publish that in this blog? Please send an email to all drops.
  • As English is not my native language, I apologize for the errors. Corrections are welcome.
  • Contact: contact [@] alldrops [.] info.

Read more on all drops: