Building Talos Linux for Raspberry Pi 5 with Custom Kernel

Getting Talos Linux running on the Raspberry Pi 5 requires a custom kernel build. The stock Talos kernel doesn’t include the necessary drivers for RPi5’s RP1 PCIe controller and MACB ethernet. This post documents the complete build process and the issues I encountered along the way.

The Problem

The RPi5 uses a new architecture with the RP1 southbridge chip connected via PCIe. This means:

  • Different ethernet driver (MACB + Broadcom BCM54213PE PHY)
  • Different device tree files (bcm2712 instead of bcm2711)
  • Network interface named end0 instead of eth0
  • Potential 4K vs 16K page size conflicts

Prerequisites

  • Docker with buildx support
  • Local container registry (I used localhost:5001)
  • ~50GB disk space for builds
  • ARM64 cross-compilation support

Repository Structure

I created a monorepo with Talos and related projects as submodules:

talso-rpi5/
├── checkouts/
│   ├── pkgs/              # kernel builds
│   ├── talos/             # main OS
│   └── sbc-raspberrypi5/  # RPi5 overlay
├── _out/                  # Build outputs
├── CLAUDE.md              # Build documentation
└── AGENTS.md              # Task delegation docs

Step 1: Build the Custom Kernel

The kernel needs specific configuration for RPi5:

# Clear Docker cache to ensure fresh build
docker builder prune -af

# Build kernel for arm64
cd checkouts/pkgs
gmake kernel PLATFORM=linux/arm64 PUSH=true \
  REGISTRY=localhost:5001 USERNAME=wittenbude

Critical Kernel Config Settings

In checkouts/pkgs/kernel/build/config-arm64:

# Use 4K pages (16K causes Talos mount API issues)
CONFIG_ARM64_4K_PAGES=y
# CONFIG_ARM64_16K_PAGES is not set

# SD card drivers must be built-in (not modules)
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_BRCMSTB=y

# Ethernet support
CONFIG_MACB=y
CONFIG_PHYLIB=y
CONFIG_PHYLINK=y
CONFIG_BROADCOM_PHY=y

The 4K vs 16K page dilemma:

  • 16K pages: Networking works, but Talos shadow bind mounts fail with EINVAL
  • 4K pages: Talos boots cleanly, networking requires matching kernel and DTB versions

Step 2: Build the Imager

The imager creates the final bootable image. It needs to reference our custom kernel:

cd checkouts/talos

# Get the kernel tag
KERNEL_TAG=$(cd ../pkgs && git describe --tag --always --dirty)

# Build imager with custom kernel
gmake imager PLATFORM=linux/arm64 PUSH=true \
  REGISTRY=localhost:5001 USERNAME=wittenbude \
  PKG_KERNEL=localhost:5001/wittenbude/kernel:$KERNEL_TAG

Step 3: Build the RPi5 Overlay

The overlay provides RPi5-specific device tree files and firmware:

cd checkouts/sbc-raspberrypi5

# Get kernel tag for PKGS reference
KERNEL_TAG=$(cd ../pkgs && git describe --tag --always --dirty)

# Build overlay with our custom kernel's DTBs
gmake PLATFORM=linux/arm64 PUSH=true \
  REGISTRY=localhost:5001 USERNAME=wittenbude \
  PKGS_PREFIX=localhost:5001/wittenbude PKGS=$KERNEL_TAG

This ensures the DTB files match the kernel version - critical for RP1 initialization.

Step 4: Generate the Metal Image

cd checkouts/talos

IMAGER_TAG=$(git describe --tag --always --dirty)
OVERLAY_TAG=$(cd ../sbc-raspberrypi5 && git describe --tag --always --dirty)

docker run --rm -t --network=host \
  -v ./_out:/out -v /dev:/dev --privileged \
  localhost:5001/wittenbude/imager:$IMAGER_TAG \
  metal --arch arm64 \
  --board=rpi_generic \
  --overlay-name=rpi5 \
  --overlay-image=localhost:5001/wittenbude/sbc-raspberrypi5:$OVERLAY_TAG \
  --extra-kernel-arg="console=tty1" \
  --extra-kernel-arg="console=ttyAMA10,115200" \
  --system-extension-image=ghcr.io/siderolabs/iscsi-tools:v0.1.10

Output: _out/metal-arm64.raw.zst (~88MB)

Step 5: Flash and Boot

# Identify SD card
diskutil list

# Unmount
diskutil unmountDisk /dev/disk4

# Flash (use rdisk for faster writes)
zstd -d -c _out/metal-arm64.raw.zst | sudo dd of=/dev/rdisk4 bs=4m status=progress

# Eject
diskutil eject /dev/disk4

Step 6: Apply Worker Configuration

Once booted, the node enters maintenance mode. Apply the worker config:

# In maintenance mode (first boot)
talosctl apply-config --insecure --nodes <NODE_IP> --file worker.yaml

# After leaving maintenance mode
talosctl -n <NODE_IP> apply-config --file worker.yaml

Critical: Enable Discovery

The worker config must include cluster discovery settings, or the node won’t appear in cluster membership:

cluster:
  id: <CLUSTER_ID>
  secret: <CLUSTER_SECRET>  # Required for discovery auth
  controlPlane:
    endpoint: https://<CONTROL_PLANE_IP>:6443
  clusterName: <CLUSTER_NAME>
  discovery:
    enabled: true  # THIS IS CRITICAL
    registries:
      service:
        endpoint: https://discovery.talos.dev/
      kubernetes:
        disabled: true

Without discovery.enabled: true and the cluster.secret, the node boots and kubelet runs, but talosctl get members won’t show it.

Network Interface

RPi5 uses end0 for ethernet (not eth0):

machine:
  network:
    interfaces:
      - interface: end0
        dhcp: true

UART Debugging

For boot issues, connect a UART adapter to the GPIO pins:

# Start logging session
screen -L -Logfile /tmp/uart.log /dev/cu.usbserial-* 115200

Key boot messages to watch for:

  • BL31 - ARM Trusted Firmware starting
  • mmc0: new - SD card detected
  • macb - Ethernet driver loading
  • BCM54213PE - PHY detected
  • machined - Talos starting

Troubleshooting

No Network Activity (NIC lights off)

  • Check kernel/DTB version mismatch
  • Verify RP1 PCIe initialization in dmesg
  • Consider 16K page kernel if RP1 isn’t initializing

Node Not Appearing in Cluster

  • Check talosctl get discoveryconfig - must show discoveryEnabled: true
  • Verify cluster.secret matches control plane
  • Check talosctl get members from the node itself

Kubelet Certificate Errors

  • Verify cluster.ca.crt matches the control plane’s certificate
  • Extract correct CA: talosctl -n <CP_IP> get machineconfig -o yaml | grep -A1 "cluster:" | grep crt

Boot Loop at BL31

  • Usually indicates 16K page kernel incompatibility
  • Rebuild with CONFIG_ARM64_4K_PAGES=y

Final Result

After all this, I have a working RPi5 node in my Talos cluster:

$ kubectl get nodes -o wide
NAME                  STATUS   ROLES           VERSION   INTERNAL-IP      KERNEL-VERSION
talos-192-168-150-8   Ready    <none>          v1.35.0   192.168.150.8    6.12.67-talos
talos-ek0-5dx         Ready    control-plane   v1.35.0   100.78.183.103   6.18.2-talos
talos-lwn-dba         Ready    <none>          v1.35.0   100.95.115.98    6.18.2-talos

The RPi5 is running the custom 6.12.67-talos kernel with 4K pages, working ethernet, and is fully participating in cluster workloads.

Repository

The build configuration and documentation are available at:

Submodule Forks (rpi5-support branch)

References