Trying to setup a service using Docker in a Raspberry Pi, I’ve found myself dealing with astronomical build times (I stoped counting after 20 minutes), many times resulting in errors due to incompatibility or network issues.
This post presents a complete and automated solution for this problem, using GitHub Actions to automatically build multi platform images and pushing them to Docker Hub.
Running Docker on Raspberry Pi
Docker is an excelent tool for creating and managing containers, greatly simplifying the process of setting up a project topology and all its depencies (DB, cache, static hosting, etc…).
In 2016, it was announced at the Raspberry Pi Blog that the
new raspbian/jessie
version would count with official support for Docker,
allowing it to be installed with relatively ease:
Unfortunately, due to the reduced processing power of the Raspberry Pi, specially older hardware versions, building a container image is extremely slow.
In one instance, I waited more then 20 minutes for it to finish, just to see it being cancelled due to a temporary network issue with the NPM repositories.
Multi Platform Builds
The solution I found is using the buildx
command, which is
available via the CLI since version 19.03
.
The command allows the creation of builders, capable of building images from
Dockerfile
s. The command, allied with QEMU, is capable of building
multi platform images, including arm/v6
and arm/v7
,
both supported by the Raspbery Pi.
I tested this method by generating an image and storing it in a .tar
file:
After finished, the image can be copied to the Raspbery Pi and loaded using:
Automatic Builds using GitHub Actions
The previous procedure is enough to solve the initial problem, but it’s still manual and doesn’t fit a continuously developed project.
A good way to solve this is by automating it and including it as a Continous Integration (CI) routine. GitHub Actions is a very good solution for that, being very easy to configure and integrating seamlessly with the repositories.
GitHub Actions allows us to configure Workflows. These are defined
through .yml
files and represent a list of actions to be executed
sequentially. These files should be located in the .github
directory, in the
root of the repository:
This file is defined as:
-
The
name
directive simply gives the workflow a name. on
defines the conditions in which the action will be executed:push
- Defines that the workflow will be executed every time there’s a push to themain
branch (If it’s an older repository, it probably uses themaster
branch).workflow_dispatch
- Allows manual execution of the workflow, in theActions
tab, in the project page.
jobs
defines the jobs to be executed and it’s parameters:build
- is the name of the job being defined.runs-on
- defines the distribution used in the virtual machine that will execute the action:- I’ve chosen
ubuntu-18.04
, since it’s a mature and stable version, though I might consider updating it soon.
- I’ve chosen
steps
- Steps to be executed.name
- Step name.id
- ID of the step. Allows for it to be referenced in other parts of the config file.uses
- Repository where the action is defined.with
- Parameters passed to the action.
Actions Description
-
action/checkout
- Checkouts the repository into the VM. docker/setup-qemu-action
- Setups QEMU:platforms
defines the platforms to be configured:- In this case, I’ve opted for
linux/amd64
andlinux/arm/v6
. - For a complete list of supported platforms, check out the action repository.
- In this case, I’ve opted for
-
docker/setup-buildx-action
- Setupsbuildx
. docker/login-action
- Login on Docker Hub:- This action will allow pushing the generated image directly to Docker Hub.
- Since this config file is commited to the repository, it’s not a good idea to store credentials in plaintext, even if it’s a private repository.
- To store credentials safely, it’s possible to use GitHub Secrets and reference them through variables prefixed with
secrets.
.
build-push-action
- Generates the Docker Image:- Once again, platforms are specified in
platforms
. tags
specifies which tag is applied to generated images:- I’ve chosen only the
:latest
tag, which is overriden every time a new image is generated. - I haven’t explored much yet, but it’s probably possible to use variables to assign the same tag of the commit being processed.
- I’ve chosen only the
- Once again, platforms are specified in
- The
run
directive allows for the execution of a shell command on the VM:- In this case, the
echo
prints the hash of the image generated in the previous step. It’s an example on how to use the IDs to refer to previous steps (steps.<step_id>
).
- In this case, the
- To discover other available actions, please take a look at the GitHub Marketplace.