Container-based builds of Raspberry Pi using Ansible

I needed a way to collaborate on raspberry pi development. I wanted to make it possible for others to reproduce my builds without demanding that they manually execute command after command to reproduce my project. I also wanted a solution that saves time by caching redundant downloads. So I built the “Builderhotspot

There is a disadvantage to using the builderhotspot- not everyone has at least 2 raspberry pi’s.

I needed a solution that works if you have only one pi, so I turned to docker. I’ve created a docker-compose file that will spin up an ansible container & an apt-cacher-ng container which can be used to push firmware images to any devices on your network with the hostname of “ansibledest.local”

This tutorial assumes you have docker installed on a host system- and that somewhere on your network is a raspberry pi attached under the hostname of “ansibledest.local”

Step 1: Clone the FirmwareBuilderContainers project into your directory of choice.  

On your host operating system, cd into a directory where you want to host your Firmware Builder Containers.  I use ~/Development/containers.

git clone git@github.com:CaptainMcCrank/FirmwareBuilderContainers.git

Step 2: Modify the Docker-compose script to reflect the details of your host system

There are 3 modifications you’ll need to make to the docker-compose file you just cloned:

DOCKER_HOST

The docker-compose file’s DOCKER_HOST variable tunes the recipient device to use your local apt-cache-ng container. This reduces redundant apt-get install downloads.  It sets the server hostname values in the /etc/apt/sources.list and /etc/apt/sources.list.d/raspi.list files on the recipient device so that it pulls downloads from the caching server.   My firmware recipes use this value as control logic.  

The value will be “builderhotspot.local” if you use the builderhotspot to push recipes to devices. Since we’re going to use containers- we need to set the value to reflect your host os’s hostname.  To get the hostname of a system, you can use different commands on Windows, macOS, and Linux. Here are the commands for each operating system:

Windows

Command Prompt:

hostname

PowerShell:

hostname 
or 
$env:COMPUTERNAME

Mac & Linux

hostname

Alternatively, you can also use the uname -n command to retrieve the hostname on a linux system. Open the docker-compose.yml file and browse to the section for the ansible container. Note the “environment:” section. Change the DOCKER_HOST value to reflect your host system’s hostname.

VOLUMES

We need to expose two directories from the host system to the ansible container.  This is done by specifying a volume & indicating where in the container it should be accessible.

The first volume is our playbooks directory. This is where we will story playbooks for all the projects we want to push to devices. You can git clone playbooks for other projects into this directory from the Host operating system. They will be exposed in the /home/pi/Playbooks directory on your ansible container.

Change the first volume’s value to reflect the correct directory on your host system. Be sure to retain

:/home/pi/Playbooks 

at the end of the first line. This is how we specify the location. My playbooks are designed to run on both the builderhotspot as well as via containers- but if you modify this second value, the playbooks won’t work without modifications.

The second volume is for enabling mdns resolution.  In our playbooks we want to use hostnames to specify the recipient device. This makes pushing a playbook to a target device easy. You don’t have to discover the recipient device’s IP address. You only need to know it’s hostname. This keeps life simple.  If you’re on linux, the best way to do this is to share the avahi-daemon socket on the host system as a volume. If you skip this step, name resolution won’t work within the ansible container. I don’t think you need to change this value on a mac- it seems to work on my system.  I still need to test this on a Windows machine to confirm mdns works. works.     

Step 3: Build & launch the containers

cd ~/Development/containers/FirmwareBuilderContainers

Build the containers & run them detached.

docker-compose up --build -d 

Step 4: Attach to the ansible container & test connectivity: 

docker exec -it ansible bash

If you have an ansibledest system running on your network, you should be able to ping it:

root@docker-desktop:/# ping ansibledest.local
PING ansibledest.local (192.168.6.247) 56(84) bytes of data.
64 bytes from 192.168.6.247 (192.168.6.247): icmp_seq=1 ttl=63 time=0.898 ms
64 bytes from 192.168.6.247 (192.168.6.247): icmp_seq=2 ttl=63 time=1.12 ms

Step 5: Detach from your containers & deactivate the containers:

From within the ansible container, type “exit” to leave the container. Then use the docker-compose command to deactivate the containers:

docker-compose down

Step 6 (From this point forward, the only commands you’ll really need when building): 

From now on, we skip the build commands. Run the docker-compose command from within the container directory on your host system:

docker-compose up
docker-exec -it ansible bash

Step 7: Building a firmware example

You now should be good to go to use Docker Containers for pushing my firmware recipes to your devices. To try out the “hack this wifi” firmware, Go to your Host OS’s terminal and cd into the playbook directory ( I use /Users/Patrick/Development/Playbooks/DockerVolume). Run the following command:

git clone  https://github.com/CaptainMcCrank/Learn_Linux_Networking_And_Hacking.git

Attach to your container:

docker-exec -it ansible bash

And now cd into your playbooks directory:

cd /home/pi/Playbooks/

If you run ls, you should see the “Learn_Linux_Networking_And_Hacking” directory. cd into it:

cd /home/pi/Playbooks/Learn_Linux_Networking_And_Hacking

If your ansibledest system is online, you can copy the container’s ssh key to the destination system, which enables you to use ansible to install software on the recipient device:

ssh-copy-id pi@ansibledest.local

And now you can deploy the firmware:

ansible-playbook run.yml

If for some reason you forgot to set your hosts file, you can fix this in ansible for your container’s session with the “export DOCKER_HOST=hostname.local” command- where hostname.local is your host system’s hostname.