Raspberry Pi image with HashiCorp Packer

In this post I will show you how to create a custom Raspberry PI Images with HashiCorp Packer. The image will have SSH enabled and docker already installed when you boot up your Raspberry Pi. Everything is done in Ubuntu. (20.11.2019)

My Setup:

  • Windows 10 with a clean Ubuntu 18.04.3 server installation in Hyper-V
  • Raspberry Pi 4

Steps:

Install HashiCorp Packer

Download Packer from HashiCorp in Version 1.3.5. Do not install version 1.4.5. since it is not compatible with the ARM builder version we are going to use for Packer. Unzip the package in /usr/local/packer.

# Update apt-get dependencies
sudo apt-get update

# Install unzip
sudo apt-get install -y unzip

# Download Packer from HashiCorp
wget https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip

# Unpack Packer
sudo unzip packer_1.3.5_linux_amd64.zip -d /usr/local/packer

# Cleanup your workspace and delete the archive
rm packer_1.3.5_linux_amd64.zip

# Add it to the PATH
sudo -i
printf '#!/bin/bash \nexport PATH=$PATH:/usr/local/packer \n' > /etc/profile.d/packer.sh

# Logout by pressing STRG+D
source /etc/profile.d/packer.sh

# Check if Packer works
packer version

Install GoLang

Install GoLang above version 1.11 since it adds module support.

# Download Go from google
wget https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz

# Unpack go
tar xvf go1.13.4.linux-amd64.tar.gz

# Change the owner
sudo chown -R root:root ./go

# move it
sudo mv go /usr/local

# Cleanup your workspace and delete the archive
rm go1.13.4.linux-amd64.tar.gz

# Add it to the PATH
sudo -i
printf '#!/bin/bash \nexport GOPATH=$HOME/work \nexport PATH=$PATH:/usr/local/go/bin:$GOPATH/bin \n' > /etc/profile.d/go.sh
# Logout by pressing STRG+D
source /etc/profile.d/go.sh

# Check if Go works
go version

Install Packer ARM builder

# Install dependencies for ARM builder
sudo apt-get install -y kpartx qemu-user-static

# Clone repository
git clone https://github.com/solo-io/packer-builder-arm-image

# Checkout the commit that worked for me
cd packer-builder-arm-image
git checkout b1c268e9d33105634c899e9095f0af097e1af432

# Build the Packer ARM builder
go mod download
go build

# Copy the finished build to Packer directory
sudo cp packer-builder-arm-image /usr/local/packer/

# Cleanup your workspace and delete the source
cd ..
sudo rm -r packer-builder-arm-image/

Create the ARM image with Packer

Now it is time to build the image for the Raspberry PI. The image will have SSH enabled and docker already installed. It is necessary to be root user. Create a json file for packer.

sudo -i
nano raspberrypi-docker.json

And this content:

{
   "variables": {
   },
   "builders": [
     {
       "type": "arm-image",
       "iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-09-30/2019-09-26-raspbian-buster-lite.zip",
       "iso_checksum_type": "sha256",
       "iso_checksum": "a50237c2f718bd8d806b96df5b9d2174ce8b789eda1f03434ed2213bbca6c6ff",
       "target_image_size": 454279954
     }
   ],
   "provisioners": [
     {
       "type": "shell",
       "inline": [
         "touch /boot/ssh",
         "apt-get update",
         "curl -sSL https://get.docker.com | sh",
         "sudo usermod -aG docker pi"
       ]
     }
   ]
 }

This configuration file tells packer to downloads the Raspbian OS image. Packer checks the checksum and starts the inline commands. Now run packer to build the image (as root user):

packer build raspberrypi-docker.json

Ignore errors and warnings if the image was created…

Build 'arm-image' finished.

==> Builds finished. The artifacts of successful builds are:
--> arm-image: output-arm-image/image

The image is located under ./output-arm-image/image Copy it with WinSCP or any other tool to windows. Rename it to raspberry-pi-with-ssh-and-docker.bin
Now you can use “balenaEtcher” to write the image to the Raspberry Pi.

SSH is enabled and docker should work out of the box.

Conclusion

Building an ARM Image with HashiCorp Packer isn’t that hard. With this example we get an Raspberry Pi image with enabled SSH and Docker already installed. Flashing it to the SD Card, plugging it in and everything works as expected.

Since the image is always the same, the network name will always be the same and has to be changed manually or with some script.