Multiplatform Images: Customization
In the previous post, I built a simple multiplatform container image. Now, we discuss how to customize the image contents depending on the target architecture.
When you build an image with precompiled libraries or your application needs different compilation instructions, Docker uses implicit arguments to let you know the target platform. I plan to walk you through the building of the DevOps "Swiss army knife" of a sort. This hypothetical set will demonstrate various types of installations by installing tools as below:
- Ansible
- Terraform
- Helm
- Kubernetes
- AWS v2 Client
Let us begin with the first part of the Dockerfile
FROM --platform=${TARGETPLATFORM} python:slim
# Tools Versions
ARG HELM_VER=v3.15.0
ARG TRFM_VER=1.8.3
ARG K8CL_VER=v1.30.1
# Builder Arguments
ARG TARGETPLATFORM
ARG TARGETOS
ARG TARGETARCH
The declarative part defines six arguments, three of which are custom, to determine the tool version we will use for Helm, Terraform, and Kubernetes CLI, respectfully. The builder driver sets those arguments, indicating the target image platform, OS name, and processor architecture. For example, my OS (MacOS, M2) default values are:
TARGETPLATFORM=linux/arm64
TARGETOS=linux
TARGETARCH=arm64
The argument --platform in the FROM directive is redundant because the builder sets it for you by default.
Now, let's add the regular OS update and packaged installations.
# check Buidler Shell
SHELL ["/bin/bash", "-c"]
RUN apt-get update; \
apt-get install -y curl wget unzip && \
python3 -m pip install --upgrade pip && \
python3 -m pip install ansible
The same command runs the Ansible installation to save some space. Those commands are platform-agnostic and will be run in the respectful architectures during the image build.
Now, the fun begins. We are adding layers of precompiled utilities that match the target build platform.
# Terraform Install
RUN curl -L "https://releases.hashicorp.com/terraform/${TRFM_VER}/terraform_${TRFM_VER}_${TARGETOS}_${TARGETARCH}.zip" -o terraform.zip
RUN curl -LO "https://releases.hashicorp.com/terraform/${TRFM_VER}/terraform_${TRFM_VER}_SHA256SUMS"
RUN unzip terraform.zip
RUN install -o root -g root -m 0755 terraform /usr/local/bin/terraform
# Get latest kubectl
RUN curl -LO "https://dl.k8s.io/release/${K8CL_VER}/bin/${TARGETPLATFORM}/kubectl"
RUN curl -LO "https://dl.k8s.io/release/${K8CL_VER}/bin/${TARGETPLATFORM}/kubectl.sha256"
# Install helm
RUN curl -LO https://get.helm.sh/helm-${HELM_VER}-${TARGETOS}-${TARGETARCH}.tar.gz && \
tar -zxvf helm-${HELM_VER}-${TARGETOS}-${TARGETARCH}.tar.gz && \
mv ${TARGETOS}-${TARGETARCH}/helm /usr/local/bin/helm
The code above heavily uses Dockerfile arguments to download and install the right binary file for the target platform. However, not all vendors use the same language, and AWS command-line tools require name translation.
# Install AWS Client
RUN export AWSARCH=$(echo "${TARGETARCH}" | sed 's#arm#aarch#;s#amd#x86_#'); \
curl https://awscli.amazonaws.com/awscli-exe-${TARGETOS}-${AWSARCH}.zip -o awscliv2.zip && \
unzip awscliv2.zip && \
./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update && \
rm -rf ./aws
CMD [/bin/bash]
Docker does not perform variable calculations, so I used BASH commands and syntax to translate the Docker architecture name into the AWS notation. It will build working images for MacOS and Linux/x86-64.
Let's recall the build command line from the previous example and build our image.
$ docker buildx build --load \
--platform=linux/arm64,linux/amd64 \
-t mikhailidim/devops-tools:0.1.0 .
A few takeaways:
- As expected, the multiplatform image build is more complex but easy to learn.
- You should be mindful of the target architecture and use builder arguments for the correct libraries and parameters.
- The layer parameters may be non-trivial and require precalculation or shell script helpers to alter the building process.
- Consider multi-stage builds to leave behind all unnecessary layers.