I have a cluster of Raspberry Pi 4’s that I put together to help me learn more about: networking, infrastructure-as-code (IAC), configuration management (CM), continuous integration / continuous deployment (CI/CD), containers (Docker), and orchestration (Kubernetes), etc.
Since this cluster is for lab purposes, I had a need to be able to quickly reprovision these machines with a fresh image. The default Raspbian (now “Raspberry Pi OS”) disk image would not work for this purpose; as it has a setup tool that gets launched on boot up, and it requires a lot of manual human interaction to get everything to a configured state.
I tried out using PXE booting on the Raspberry Pi 4 (which I’ll write an article about at some point), but ultimately found various issues with running things this way. I also wasn’t keen on having to keep a TFTP server available for housing the boot images for all the Raspberry Pis.
After much research, I decided to just create my own customized version of the default Raspbian image. This allows me to flash the image from my Windows desktop to either an SD card or USB device, pop/plug it into the Raspberry Pi, and start using it immediately.
- Download a copy of the Raspbian / Raspberry Pi OS disk image:
- Have access to a Linux machine or VM
Note: I used the “2020-05-21-rasbian-buster” release, please update your steps appropriately if you use a newer image.
For a better look at how I customized my Raspbian image, I recommend viewing my public repository here:
1. Copy the original zip file containing the Raspbian disk image and unzip the new file:
cp 2020-05-21-raspbian-buster.zip 2020-05-21-raspbian-buster_custom.zip
2. Get the sectors of the image:
fdisk -lu 2020-05-21-raspbian-buster_custom.img
Disk 2020-05-21-raspbian-buster_custom.img: 3.54 GiB, 3787456512 bytes, 7397376 sectors
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: 0xea7d04d6
Device Boot Start End Sectors Size Id Type
2020-05-21-raspbian-buster_custom.img1 8192 532479 524288 256M c W95 FAT32 (LBA)
2020-05-21-raspbian-buster_custom.img2 532480 7397375 6864896 3.3G 83 Linux
3. Determine the offset by multiplying the sector size (
512) by the start sector:
8192 * 512 = 4194304
532480 * 512 = 272629760
4. Make directories for mounting the boot and root directories:
sudo mkdir /mnt/raspbian-boot /mnt/raspbian-root
5. Mount the /boot directory using the offset gather before:
sudo mount -o loop,offset=4194304 2020-05-21-raspbian-buster_custom.img /mnt/raspbian-boot/
6. Make any desired changes to the files in the /boot directory.
In my case, I added support for aarch64 (64 bit) kernels by adding
arm_64bit=1 to the
7. Unmount the /boot directory:
sudo umount /mnt/raspbian-boot/
8. Mount the root directory using the offset gathered earlier:
sudo mount -o loop,offset=272629760 2020-05-21-raspbian-buster_custom.img /mnt/raspbian-root/
9. Install qemu utilities and restart binfmt:
sudo apt-get install qemu-user-static
sudo systemctl restart systemd-binfmt.service
10. Use chroot with the qemu arm emulator:
sudo chroot /mnt/raspbian-root/ qemu-arm-static /bin/bash
11. Make any desired chances to the files in the root directory.
In my case, I removed the link to the setup tool, replaced the
pi user with one named
jeff, and added some custom bash scripts to the
/home/jeff directory to be run on the initial first start of the OS (enable WIFI, update hostname by DHCP, regenerate SSH server keys, etc.).
12. Unmount the root directory:
sudo umount /mnt/raspbian-root/
13. Repackage the image file into a zip:
zip $(date +%Y%b%d)-raspbian-buster_custom.zip 2020-05-21-raspbian-buster_custom.img
14. Burn the image file to an SD card, USB stick, or external HDD/SSD (using a USB adapter) with either
dd or a utility like balenaEtcher.