Create Custom ArchlinuxArm Images for the Raspberry Pi

21 Mar 2016

I generally work with headless Raspberry Pis either by running them as lightweight servers or embedding them into projects. It is very anoying having to plug them into a monitor keyboard and mouse to set them up and quite often impossible if not at home.

I use to get around this by using a serial cable to configure them after flashing the image to the sd-card but that has its own disadvantages. Recently I discovered how to chroot into a Raspberry Pi image using qemu and binfmt-support making it possible to setup a pi before you boot it. In this post I will take this one step further and look at what it takes to setup a raspberry pi image/sd-card and customise it before you even have to plug it into a pi.


You will require a Linux box with the following packages need to be installed

  • qemu
  • qemu-user-static
  • binfmt-support

See this for setting them up on Arch Linux.

You can alternatively use Vagrant with the following Vagrantfile to give you a Linux virtual machine with all the prerequisites preinstalled. There are some caveats with using Vagrant for this process which are out of scope of this post. If there is enough interest I may cover Vagrant in a future post.

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config| = "ubuntu/trusty64"
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update -y
    apt-get install -y qemu qemu-user-static binfmt-support

Create an image file

If you want to write directly to an sd-card skip this step and head to Format and Mount the Device. Remember to substitute /dev/loop0 with the device you want to write to, typically /dev/mmblkX or /dev/sdX for sd-cards.

Creating an image file is simple, just create an empty file large enough to store everything we want to install. A simple way to do this is with fallocate to create a 2 gigibyte file run the following.

fallocate -l 2G "custom-pi.img"

Feel free to play with the image size but 2G is a good starting point. Just remember that it needs to be smaller then the size of the sd-card you want to use but large enough to store the operating system and any applications you want to install. The smaller the better as it takes less time to write the image to your sd-card later.

Now setup the image as a loopback device so we can format and mount it as any physical disk. Note the device that this command returns, we will need it later - in the example below its /dev/loop0.

sudo losetup --find --show "custom-pi.img"
> /dev/loop0

Format and mount the device

Here we create the partitions the first one, 100M in size, for the boot files and the second one for the rest of the system.

sudo parted --script /dev/loop0 mklabel msdos
sudo parted --script /dev/loop0 mkpart primary fat32 0% 100M
sudo parted --script /dev/loop0 mkpart primary ext4 100M 100%

This will create two new devices /dev/loop0p1 and /dev/loop0p2 which you can see by running ls /dev/loop0?*. We can now format these partitions with vfat (required for the boot partition) and ext4 respectively.

sudo mkfs.vfat -F32 /dev/loop0p1
sudo mkfs.ext4 -F /dev/loop0p2

Before we can install archlinuxarm we must mount the partition.

sudo mount /dev/loop0p2 /mnt
sudo mkdir /mnt/boot
sudo mount /dev/loop0p1 /mnt/boot

Install the base system

Now download the archlinuxarm tar for your pi. For the raspberry pi 2/3


Or for the raspberry pi 1.


And extract it to the mounted image.

sudo tar -xpf "ArchLinuxARM-rpi-2-latest.tar.gz" -C /mnt

You may see a bunch of line like

tar: Ignoring unknown extended header keyword 'SCHILY.fflags'

These are safe to ignore.

You should now have a fully working raspberry pi image and can skip to the cleanup step below if you do not want to make any customizations to the image.

Chroot into the image

There are a few system directories that need mounting to create a successful chroot environment.

sudo mount -t proc none /mnt/proc
sudo mount -t sysfs none /mnt/sys
sudo mount -o bind /dev /mnt/dev

Then to get a working network inside the chroot we need to fix the resolv.conf file.

sudo mv /mnt/etc/resolv.conf /mnt/etc/resolv.conf.bak
sudo cp /etc/resolv.conf /mnt/etc/resolv.conf

And now for the bit that allows us to execute arm executables on a x86 or x86_64 system.

sudo cp /usr/bin/qemu-arm-static /mnt/usr/bin/

We should now be able to chroot into our raspberry pi image.

sudo chroot /mnt /usr/bin/bash

Any command you now run will affect the raspberry pi image rather then your computer.

Install and configure your install

Here is where you have the most freedom to create a custom image. You can now install what you want and configure it how you want. I will give you some suggestions but feel free to skip or add extra steps to create the image the way you want it.

Keep in mind we only have a chroot, not a fully running system so you are partially limited in what you can do. For example you cannot enable a service with systemctl enable SOMESERVICE, instead you have to create the symlinks your self ln -sf /usr/lib/systemd/system/SOMESERVICE.service /etc/systemd/system/

First thing we should do to our image is update it and install any extra packages we want.

pacman -Syu vim bash-completion

You can change the hostname and rename the default user with the following.

echo custom-pi > /etc/hostname

sed -i "s/alarm/pi/g" /etc/passwd /etc/group /etc/shadow
mv /home/alarm "/home/pi"
echo -e "secret\nsecret" | passwd "pi"

Install and enable sudo for our new user with.

pacman -S --noconfirm sudo
echo '%wheel ALL=(ALL) ALL' >> /etc/sudoers.d/wheel

To enable the raspberry pi camera do.

sed -i 's/gpu_mem=.*/gpu_mem=128/' /boot/config.txt
grep 'start_file=start_x.elf' /boot/config.txt >/dev/null || echo 'start_file=start_x.elf' >> /boot/config.txt
grep 'fixup_file=fixup_x.dat' /boot/config.txt >/dev/null || echo 'fixup_file=fixup_x.dat' >> /boot/config.txt

Setup wired and wireless networking to auto connect when the pi boots (or just roams within range of the network). Remember to replace the SSID and PASSWORD with your own wireless credentials. You can also add multiple wireless networks and the pi will connect to any that it can see allowing you to move your pi between locations.

pacman -S --noconfirm wpa_supplicant wpa_actiond ifplugd crda dialog
ln -sf /usr/lib/systemd/system/[email protected] /etc/systemd/system/[email protected]
ln -sf /usr/lib/systemd/system/[email protected] /etc/systemd/system/[email protected]

cat <<EOF >"/etc/netctl/wlan0-SSID"
Description='Baked in profile'

Enable zero-conf networking (aka avahi or Bonjour) to make discovering your pi on the network easier, if your system supports it.

pacman -S --noconfirm avahi nss-mdns
sed -i '/^hosts: /s/files dns/files mdns dns/' /etc/nsswitch.conf
ln -sf /usr/lib/systemd/system/avahi-daemon.service /etc/systemd/system/

Cleaning Up

Once you have finished setting up your pi how you want it we need to clean up some stuff. First exit the chroot by running exit or pressing ctrl+d. Then we start to unwind some of the setup steps, starting with restoring the resolve.conf, and unmounting the system folders needed by the chroot.

sudo rm /mnt/etc/resolv.conf
sudo mv /mnt/etc/resolv.conf.bak /mnt/etc/resolv.conf
sudo rm /mnt/usr/bin/qemu-arm-static

sudo umount /mnt/dev
sudo umount /mnt/proc
sudo umount /mnt/sys

Now we can unmount the partitions and detach the loopback device.

sudo umount /mnt/boot
sudo umount /mnt
sudo losetup --detach "/dev/loop0"

Flash the Image to an SD-Card

We are now ready to flash the image, which can be done with dd. Remember to replace /dev/mmblk0 with the device for your sd-card.

sudo dd if=custom-pi.img of=/dev/mmblk0 bs=1M


Thats it, you are now ready to boot your pi. If you followed the above exactly your pi should boot and connect to your chosen network and be ready to ssh into. However if you encounter problems with connecting you may need to dig out your serial cable or attach your pi to a keyboard and monitor after all to debug it.

You may have noticed that the procedure lends itself well to being scripted making the creating an custom image repeatable. That way you can commit the scripts to your projects repository and regenerate an image when you want, rather then having to keep around a bunch of 2G+ backup images that you have manually setup. I hope to look at scripting this process in a future post to make creating repeatable images even easier.