Working with Raspberry Pi images
I’ve found myself working with the Raspberry Pi frequently recently. It’s a nice platform, and I appreciate the software tooling for making it easy to get going and allowing you to focus on what you’re trying to make, rather than the embedded platform itself. But every guide points to using the Raspberry Pi Imager, which isn’t a satisfying answer for what the process actually is, which would help for figuring out how to automate the process.
dd is always our friend
Raspberry Pi OS, their Debian-based distribution, is available as a
compressed image file, a .img.xz. Find the device (fdisk -l), then we can
write the image:
$ unxz 2025-12-04-raspios-trixie-arm64-lite.img.xz
$ sudo dd if=2025-12-04-raspios-trixie-arm64-lite.img of=/dev/sda
5832704+0 records in
5832704+0 records out
2986344448 bytes (3.0 GB, 2.8 GiB) copied, 268.014 s, 11.1 MB/s
I’ve been skipping this step for some projects and using a full image by buying a pre-installed SD card, which is a helpful option.
Customisation
The disk image is made up of two partitions: a smaller FAT32 partition which is
mounted at /boot and a larger Linux partition which holds the operating
system image:
$ sudo fdisk -l
[ ... snip ... ]
Disk /dev/sda: 58.94 GiB, 63281561600 bytes, 123596800 sectors
Disk model: SD/MMC
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xffc763c1
Device Boot Start End Sectors Size Id Type
/dev/sda1 16384 1064959 1048576 512M c W95 FAT32 (LBA)
/dev/sda2 1064960 5832703 4767744 2.3G 83 Linux
The /boot partition contains files like the config.txt, used to configure
hardware options, plus the binaries and relevant device trees for booting the
operating system. In addition, at the end of 2025, Raspberry Pi released
cloud-init support. This allows us to drop a couple of YAML files onto
the /boot partition of the image, which are read and processed on boot.
$ sudo mount /dev/sda1 /media/usb-drive
$ ls /media/usb-drive
bcm2710-rpi-2-b.dtb bcm2711-rpi-400.dtb bcm2712-rpi-500.dtb cmdline.txt fixup.dat kernel8.img start4.elf
bcm2710-rpi-3-b.dtb bcm2711-rpi-4-b.dtb bcm2712-rpi-5-b.dtb config.txt fixup_db.dat LICENCE.broadcom start4x.elf
bcm2710-rpi-3-b-plus.dtb bcm2711-rpi-cm4.dtb bcm2712-rpi-cm5-cm4io.dtb fixup4cd.dat fixup_x.dat meta-data start_cd.elf
bcm2710-rpi-cm0.dtb bcm2711-rpi-cm4-io.dtb bcm2712-rpi-cm5-cm5io.dtb fixup4.dat initramfs_2712 network-config start_db.elf
bcm2710-rpi-cm3.dtb bcm2711-rpi-cm4s.dtb bcm2712-rpi-cm5l-cm4io.dtb fixup4db.dat initramfs8 overlays start.elf
bcm2710-rpi-zero-2.dtb bcm2712d0-rpi-5-b.dtb bcm2712-rpi-cm5l-cm5io.dtb fixup4x.dat issue.txt start4cd.elf start_x.elf
bcm2710-rpi-zero-2-w.dtb bcm2712-d-rpi-5-b.dtb bootcode.bin fixup_cd.dat kernel_2712.img start4db.elf user-data
The Pi can be configured like any other using cloud-init, but it’s also got
a Raspberry Pi specific module which is handy for configuring hardware,
including support for the fairly new USB gadget mode. Using cloud-init
makes for a nice path for future automation.
I’m bootstrapping Pis with something that looks like this as the baseline for
user-data (I’m leaving meta-data and network-config the same as the
default behaviour is fine):
#cloud-config
hostname: test-pi
manage_etc_hosts: true
timezone: Europe/London
users:
- name: pi
groups: users,adm,dialout,audio,netdev,video,plugdev,cdrom,games,input,gpio,spi,i2c,render,sudo
shell: /bin/bash
lock_passwd: false
passwd: $6$rounds=4096$MwcQMjo1p9YjlEBx$NkyMyUaGVa8hdD33aO/9XS/NoHdJ1a/ekfXOaF2QbYAWyt8eC7weNTAu9N2aS1Uk4zj1BNnD/tRr19nNoe/190
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFypAVlkyYVwdFAUrDnAcZqIM6Lkusv+9J3rRUn7qZzA
sudo: ALL=(ALL) NOPASSWD:ALL
rpi:
enable_usb_gadget: true
enable_ssh: true
The password used above is password, but generated using mkpasswd
--method=SHA-512 --rounds=4096, as recommended by cloud-init. If you
set a password, you also need to set lock_passwd: false, otherwise you can
never login. enable_ssh: true is a Raspberry Pi-specific option, as
otherwise SSH isn’t up when it first boots.