Alpine Linux Installation on Raspberry Pi | Minimal, efficient containerization on ARM hardware

 February 2024    9 min read

DocsLinux
<h1 data-number="1" id="why-alpine-on-raspberry-pi"><span class="header-section-number">1</span> Why Alpine on Raspberry Pi</h1> <p>Alpine Linux runs exceptionally well on ARM hardware because it aligns with ARM’s design philosophy: minimal instruction sets, low power consumption, and resource efficiency. Alpine’s userspace mirrors this approach: busybox instead of GNU coreutils, musl instead of glibc.</p> <p>In practice, a 4GB Raspberry Pi 4 runs Alpine with minimal overhead. The kernel uses roughly 70MB of RAM, Alpine’s userland adds only a few MB more, and you’re left with substantial headroom for Docker containers and services. This efficiency justifies the tradeoff of incompatibility with some x86-centric software.</p> <p>Alpine’s official Raspberry Pi images are well-maintained and ship with bootloader and kernel optimizations pre-configured. However, the installation process differs from Debian/Ubuntu approaches, and recent versions have introduced breaking changes in boot configuration. Understanding the process prevents surprises later.</p> <h1 data-number="2" id="prerequisites"><span class="header-section-number">2</span> Prerequisites</h1> <p>You need: - Raspberry Pi 4 (ARM64, 2GB minimum, 4GB recommended) - SD card (16GB minimum, 32GB recommended) - Laptop for flashing the image - Serial console access (optional but useful for debugging)</p> <p>Download the latest Alpine Linux ARM64 image from <code>alpinelinux.org</code>. Avoid older versions—boot configuration has changed, and updating from legacy Alpine often breaks the system.</p> <p>Flash the image using <code>dd</code> or Balena Etcher:</p> <pre><code>sudo dd if=alpine-latest-aarch64.iso of=/dev/sdX bs=4M status=progress sync</code></pre> <p>Replace <code>/dev/sdX</code> with your SD card device. Verify with <code>lsblk</code> first.</p> <h1 data-number="3" id="installation-workflow"><span class="header-section-number">3</span> Installation Workflow</h1> <h2 data-number="3.1" id="initial-setup"><span class="header-section-number">3.1</span> Initial Setup</h2> <p>Insert the SD card into the Pi and connect power. Alpine boots into a live environment. Log in as root (no password required).</p> <p>Run the interactive setup:</p> <pre><code>setup-alpine</code></pre> <p>This wizard configures hostname, timezone, network (DHCP for now), and package cache. For a headless system, accept defaults and skip password setup initially—you’ll secure the system after installation.</p> <h2 data-number="3.2" id="partitioning"><span class="header-section-number">3.2</span> Partitioning</h2> <p>Alpine’s default root filesystem is in RAM. To persist data, you must create a permanent root partition.</p> <p>Install partitioning tools:</p> <pre><code>apk add e2fsprogs cfdisk</code></pre> <p>Launch the partitioner:</p> <pre><code>cfdisk /dev/mmcblk0</code></pre> <p>Alpine uses MBR partitioning, supporting up to 4 primary partitions. The SD card arrives with partition 1 (boot, FAT16). Create partition 2 for the root filesystem:</p> <ul> <li>Partition 1: 256MB FAT16 (boot, already exists)</li> <li>Partition 2: Remainder ext4 (root)</li> </ul> <p>After partitioning, format the root partition:</p> <pre><code>mkfs.ext4 /dev/mmcblk0p2</code></pre> <h2 data-number="3.3" id="boot-restructure"><span class="header-section-number">3.3</span> Boot Restructure</h2> <p>The key step many guides miss: Alpine’s boot must be explicitly configured for the Pi. The standard <code>setup-disk</code> command does not account for ARM bootloader requirements.</p> <p>Mount the new root filesystem:</p> <pre><code>mount /dev/mmcblk0p2 /mnt</code></pre> <p>Set a flag to force Alpine to use the FAT boot partition:</p> <pre><code>export FORCE_BOOTFS=1</code></pre> <p>Now run the disk setup:</p> <pre><code>setup-disk -m sys /mnt</code></pre> <p>This copies the Alpine root filesystem to <code>/mnt</code>, configures the bootloader, and sets up the kernel and initramfs with proper ARM naming conventions. The <code>-m sys</code> flag ensures the system runs in <q>sys</q> mode (persistent root), not <q>diskless</q> mode.</p> <h2 data-number="3.4" id="fstab-configuration"><span class="header-section-number">3.4</span> fstab Configuration</h2> <p>After installation, the system still needs to mount the boot partition explicitly on subsequent boots. Edit the fstab:</p> <pre><code>vi /mnt/etc/fstab</code></pre> <p>Add an entry for the boot partition:</p> <pre><code>/dev/mmcblk0p1 /media/mmcblk0p1 vfat defaults 0 0</code></pre> <p>This ensures the FAT boot partition is accessible if your system needs to update the kernel or bootloader later. Some use cases (like kernel debugging) require direct access.</p> <h2 data-number="3.5" id="users-and-packages"><span class="header-section-number">3.5</span> Users and Packages</h2> <p>Chroot into the new system to complete configuration:</p> <pre><code>chroot /mnt /bin/ash</code></pre> <p>Create a non-root user for day-to-day operations:</p> <pre><code>adduser admin</code></pre> <p>Set a strong password when prompted.</p> <p>Install essential packages:</p> <pre><code>apk add doas docker docker-compose git curl vim openssh</code></pre> <ul> <li><code>doas</code>: Lightweight sudo replacement</li> <li><code>docker</code>, <code>docker-compose</code>: Container runtime and orchestration</li> <li><code>git</code>, <code>curl</code>, <code>vim</code>: Development and debugging tools</li> <li><code>openssh</code>: Remote access</li> </ul> <p>Configure <code>doas</code> to allow the admin user to run privileged commands without a password:</p> <pre><code>echo &quot;permit persist :admin&quot; &gt;&gt; /etc/doas.d/doas.conf</code></pre> <p>Enable Docker to start at boot:</p> <pre><code>rc-update add docker boot</code></pre> <p>Add the admin user to the docker group so they can run containers without <code>doas</code>:</p> <pre><code>addgroup admin docker</code></pre> <p>Exit the chroot and unmount the filesystems:</p> <pre><code>exit umount -R /mnt</code></pre> <p>Now power down the Pi, remove the SD card, and insert it into the Raspberry Pi 4.</p> <h1 data-number="4" id="first-boot-and-verification"><span class="header-section-number">4</span> First Boot and Verification</h1> <p>When the Pi boots, it loads the kernel from the FAT partition and mounts the ext4 root filesystem. If everything is configured correctly, you’ll see Alpine’s boot messages and a login prompt.</p> <p>If the Pi fails to boot or displays a flashing green light without progress: - The boot partition is misconfigured - The kernel or initramfs is missing or misnamed - The fstab is preventing the root mount</p> <p>Serial console access helps diagnose these issues. If unavailable, re-insert the SD card into your laptop and verify that <code>/dev/mmcblk0p1</code> contains <code>kernel*.img</code> and <code>initramfs*.img</code> files. Recent Alpine versions use names like <code>vmlinuz-rpi4</code> and <code>initramfs-rpi4</code>; older versions used differently named files.</p> <p>Log in as the admin user you created:</p> <pre><code>ssh admin@raspberry-pi-ip</code></pre> <p>Verify Docker is running:</p> <pre><code>docker ps</code></pre> <p>If successful, you have a minimal, bootable Alpine Linux system.</p> <h1 data-number="5" id="networking-and-security"><span class="header-section-number">5</span> Networking and Security</h1> <h2 data-number="5.1" id="static-ip-configuration"><span class="header-section-number">5.1</span> Static IP Configuration</h2> <p>Alpine uses OpenRC for network management. Assign a static IP via your router’s DHCP reservation, or configure Alpine directly:</p> <pre><code>vi /etc/network/interfaces</code></pre> <p>Replace the DHCP entry with:</p> <pre><code>auto eth0 iface eth0 inet static address 192.168.1.50 netmask 255.255.255.0 gateway 192.168.1.1 dns-nameserver 1.1.1.1</code></pre> <p>Restart networking:</p> <pre><code>sudo rc-service networking restart</code></pre> <h2 data-number="5.2" id="firewall"><span class="header-section-number">5.2</span> Firewall</h2> <p>Alpine ships with netfilter but no firewall daemon. Install UFW for simple rule management:</p> <pre><code>sudo apk add ufw sudo rc-service ufw start sudo rc-update add ufw boot</code></pre> <p>Allow only SSH (port 22) and HTTPS (port 443):</p> <pre><code>sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 443/tcp sudo ufw enable</code></pre> <p><strong>Critical note on IPv6:</strong> Alpine ARM builds do not reliably support IPv6. Disable it in UFW:</p> <pre><code>sudo vi /etc/default/ufw</code></pre> <p>Set:</p> <pre><code>IPV6=no</code></pre> <p>This prevents IPv6 scanning and unexpected traffic. If you need IPv6 later, re-enable after testing thoroughly.</p> <h2 data-number="5.3" id="ssh-key-authentication"><span class="header-section-number">5.3</span> SSH Key Authentication</h2> <p>Remove password-based SSH authentication:</p> <pre><code>sudo vi /etc/ssh/sshd_config</code></pre> <p>Set:</p> <pre><code>PasswordAuthentication no PubkeyAuthentication yes</code></pre> <p>Copy your public key to the Pi:</p> <pre><code>ssh-copy-id -i ~/.ssh/id_rsa.pub admin@192.168.1.50</code></pre> <p>Restart SSH:</p> <pre><code>sudo rc-service sshd restart</code></pre> <p>Now you can SSH into the Pi without a password, and password attacks are impossible.</p> <h1 data-number="6" id="system-updates"><span class="header-section-number">6</span> System Updates</h1> <p>Alpine updates are straightforward but require care on minimal systems. Update the package cache:</p> <pre><code>sudo apk update</code></pre> <p>Upgrade packages:</p> <pre><code>sudo apk upgrade</code></pre> <p>Kernel updates are applied separately:</p> <pre><code>sudo apk add --upgrade linux-rpi4</code></pre> <p>After a kernel update, reboot:</p> <pre><code>sudo reboot</code></pre> <p>Watch the Pi boot process. If it fails, the bootloader or initramfs configuration may need adjustment. This is rare but possible when Alpine introduces breaking changes across major versions.</p> <h1 data-number="7" id="docker-and-container-management"><span class="header-section-number">7</span> Docker and Container Management</h1> <p>With Docker running and the system secured, you’re ready for containerized workloads. Alpine includes Docker and Docker Compose in its repositories. No need for external installation scripts.</p> <p>Verify Docker works:</p> <pre><code>docker run --rm alpine echo &quot;Hello from Alpine&quot;</code></pre> <p>Pull and run a basic service. For example, a Caddy reverse proxy:</p> <pre><code>docker run -d \ -p 443:443 \ -v /etc/caddy:/etc/caddy \ -v /data/caddy:/data/caddy \ caddy:latest caddy run --config /etc/caddy/Caddyfile</code></pre> <p>Docker containers on Alpine follow the same patterns as any Linux distribution. The advantage is that Alpine’s minimal host system leaves more resources for container workloads.</p> <h1 data-number="8" id="common-gotchas"><span class="header-section-number">8</span> Common Gotchas</h1> <p><strong>Boot partition permissions:</strong> If you later update the kernel and the boot partition is not mounted, new kernel files won’t be written to the FAT partition. Always ensure <code>/dev/mmcblk0p1</code> is mounted before kernel updates.</p> <p><strong>ARM64 image compatibility:</strong> Not all Docker images support ARM64. Before pulling an image, check its manifest on Docker Hub or Quay. If an image is x86-only, the pull succeeds but the container fails at runtime with <q>exec format error.</q></p> <p><strong>Memory and swap:</strong> Alpine on a 4GB Pi has ample memory for most workloads, but intensive operations (like building images locally) may benefit from swap. Create a swapfile if needed, but understand that swapping over SD card storage is slow.</p> <p><strong>OpenRC vs systemd:</strong> Alpine uses OpenRC instead of systemd. Service files are simple shell scripts in <code>/etc/init.d/</code>. If you’re used to systemd, expect a different operational model, but also expect lower overhead and faster boots.</p> <h1 data-number="9" id="next-steps"><span class="header-section-number">9</span> Next Steps</h1> <p>Your Alpine Pi now has a minimal, secure foundation. From here:</p> <ul> <li>Deploy containerized services using Docker Compose</li> <li>Set up a reverse proxy (Caddy or Traefik) for TLS termination</li> <li>Configure container management tools (Portainer, Rancher) for visibility</li> <li>Implement dynamic DNS automation to maintain public accessibility</li> <li>Add monitoring with Prometheus and Grafana</li> </ul> <p>Alpine’s efficiency makes it ideal for long-running, low-power home infrastructure. The discipline of working with minimal systems teaches valuable lessons about what’s truly essential.</p> <h1 data-number="10" id="verification-checklist"><span class="header-section-number">10</span> Verification Checklist</h1> <p>Before declaring the installation complete:</p> <ul class="task-list"> <li><label><input type="checkbox"></input>Alpine boots and logs in without password as admin user</label></li> <li><label><input type="checkbox"></input>SSH key authentication works; password authentication is disabled</label></li> <li><label><input type="checkbox"></input>Docker runs and can pull/run containers</label></li> <li><label><input type="checkbox"></input>Static IP is assigned and stable</label></li> <li><label><input type="checkbox"></input>Firewall allows only ports 22 and 443 inbound</label></li> <li><label><input type="checkbox"></input>IPv6 is disabled</label></li> <li><label><input type="checkbox"></input>fstab includes boot partition mount</label></li> <li><label><input type="checkbox"></input><code>doas</code> is configured for passwordless privilege escalation</label></li> <li><label><input type="checkbox"></input>System clock is correct</label></li> <li><label><input type="checkbox"></input>Kernel and initramfs files exist in <code>/media/mmcblk0p1</code></label></li> </ul> <p>With this checklist passed, you have a production-ready Alpine Linux foundation for containerized workloads.</p>
Previous Arch Linux Installa… Next A Domain-Addressabl…

© 2026 | CC0 1.0 Universal Codebase | Powered by django