Raspberry Pi 4 – Custom Raspbian Image


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.


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
unzip 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 /boot/config.txt file.

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.