Close

Automated Raspberry Pi Deployments with Docker Buildx and Ansible

A project log for Hardware Data Logger

Easily extendable data logging platform featuring STM32F103RBTx, WiFi, microSD storage, LCD with four buttons, UART, and pulse counters.

robert-gawronRobert Gawron 12/23/2024 at 18:210 Comments

In this post, I will describe how I set up a Docker container on my PC (x86) to cross-compile Docker containers for Raspberry Pi. I will also discuss Ansible, a tool for automating the deployment and configuration of remote machines. I use it to configure the Raspberry Pi and install the images I build locally.

Project Setup

For this cross-compilation, we need two Docker configurations: one to build target images and another to define what those target Docker images are, to configure them (what is installed in them). Here's the project structure:

├── Host
│   ├── Dockerfile
│   ├── README.md
│   ├── ansible
│   │   ├── README.md
│   │   ├── files
│   │   │   ├── docker-compose.yml -> /workspace/docker-compose.yml
│   │   ├── inventory
│   │   ├── playbook.yml
│   │   └── roles
│   ├── bake.hcl
│   ├── docker-compose.yml
│   └── scripts
│       ├── docker_export.sh
│       ├── entrypoint.sh
│       └── mount_ssh.sh
├── Target
│   ├── Dockerfile
│   ├── README.md
│   ├── Test
│   │   ├── README.md
│   │   └── test.py
│   └── docker-compose.yml

The idea is that in the Host/ we build our builder container and once we are logged into it the configs to build target (Target folder) are mounted inside it.

Host Container Setup

Surprisingly, not much needs to be done for the host container. Docker is already equipped with an image for cross-compilation (docker:dind). I created this simple Dockerfile (along with docker-compose.yml to specify which external files are available inside the container), and it works perfectly:

FROM docker:dind

# Install QEMU for ARM cross-platform builds and other necessary packages, along with Ansible
RUN apk add --no-cache \    qemu qemu-system-x86_64 qemu-system-arm \    bash curl git python3 py3-pip \    ansible \    rsync \    dos2unix

COPY ./scripts/*.sh /workspace/
RUN dos2unix /workspace/*.sh
RUN chmod +x /workspace/*.sh

# Set the working directory inside the container
WORKDIR /workspace

# Set the default command to bash
CMD ["/bin/bash"]

Once I log into the container, I have access to all the necessary files:

.
├── Dockerfile
├── README.md
├── ansible
│   ├── README.md
│   ├── files
│   │   ├── docker-compose.yml -> /workspace/docker-compose.yml
│   ├── inventory
│   ├── playbook.yml
│   └── roles
├── docker-compose.yml
├── docker_export.sh
├── entrypoint.sh
└── mount_ssh.sh

Building for Raspberry Pi

Inside this builder container, I use a docker-compose.yml configured for building images for the Raspberry Pi. The only modification from my last post (when I tested them locally on a PC) is that I hardcoded the platform: linux/arm64 directive for each service, specifying the target architecture for Buildx (this could also be configured in a .hcl file but I didn’t look into that).

To build and extract the images into .tar files, I run the following command:

docker buildx bake --file docker-compose.yml && docker_export.sh

Now we are ready to deploy those exported .tar images.

Deploying Images with Ansible

Ansible is a tool that executes a list (called a playbook) of tasks on remote machines via SSH, much like a person would. So instead of doing the configuration each time, this tool does it automatically.

These tasks can be grouped into reusable blocks (roles), many of which are open source. For example, to set up Docker and Docker Compose on the Raspberry Pi, I used the ansible-docker role.

Here is an example of how I run Ansible:

ansible-playbook -i ansible/inventory ansible/playbook.yml

What Ansible Does on the Raspberry Pi

Ansible performs the following steps on the Raspberry Pi (steps are skipped automatically if no modifications are needed):

It's all automated!

Notes


I find Ansible to be slow, however, this may be configurable to some extent.

Ansible will need an .ssh key to connect to the target machine without requiring a password. I tried to map .ssh from the building container to the external host (by git repo + add .gitignore to avoid committing those keys), to avoid redoing this step after each reconnection to the container but it failed.

The .ssh folder was always mounted with rwxrwxrwx permission (able to read, write, and execute for everyone) and the SSH agent won't accept keys from such a folder because they could be read/written by everyone (and that's true). That's why I made a small dirty fix (mount_ssh.sh) called at the startup of the container.

Summary

Overall, I think this system provides an efficient way to build and deploy Docker containers for the Raspberry Pi. If you would like to configure Grafana on Raspberry Pi using Docker, you could take a look at what I did (but I'm not sure if I did it well or badly).

Link to the commit.

Discussions