• Stars
    star
    125
  • Rank 286,335 (Top 6 %)
  • Language
    Dockerfile
  • License
    The Unlicense
  • Created over 3 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Using BuildKit and TARGETPLATFORM for cross-platform Dockerfiles

Docker --platform translation example for TARGETPLATFORM

Naming is hard. Having a consistent OS (kernel) and architecture naming scheme for building is harder.

Goal: In Docker, our goal should be a single Dockerfile that can build for multiple Linux architectures. A stretch-goal might be cross-OS (Windows Containers), but for now let's focus on the Linux kernel.

Turns out this might be harder then you're expecting.

Docker has BuildKit which makes this much easier with the docker buildx build --platform option, and combined with the ARG TARGETPLATFORM gets us much closer to our goal. See the docs on multi-platform building and the automatic platform ARGs.

The problem with downloading binaries in Dockerfiles

There are still inconsistencies we need to deal with. This problem rears its ugly head when you're trying to download pre-built binaries of various tools and dependencies (GitHub, etc.) that don't use a package manager (apt, yum, brew, apk, etc.). Download URLs are inconsistently named, and expect some sort of kernel and architecture combo in the file name. No one seems to agree on common file naming.

Using uname -m won't work for all architectures, as the name changes based on where it's running. For example, with arm64 (v8) architecture, it might say arm64, or aarch64. In older arm devices it'll say armv71 even though you might want arm/v6.

There's also the complexity that a device might have one architecture hardware (arm64) but run a different kernel (arm/v7 32-Bit).

The containerd project has created their own conversion table, which I'm commenting on here. This is similar to (but not exactly) what ARG TARGETPLATFORM gives us:

//   Value    Normalized
//   aarch64  arm64      # the latest v8 arm architecture. Used on Apple M1, AWS Graviton, and Raspberry Pi 3's and 4's
//   armhf    arm        # 32-bit v7 architecture. Used in Raspberry Pi 3 and  Pi 4 when 32bit Raspbian Linux is used
//   armel    arm/v6     # 32-bit v6 architecture. Used in Raspberry Pi 1, 2, and Zero
//   i386     386        # older Intel 32-Bit architecture, originally used in the 386 processor
//   x86_64   amd64      # all modern Intel-compatible x84 64-Bit architectures
//   x86-64   amd64      # same

So that's a start. But BuildKit seems to do additional conversion, as you'll see in the testing below.

Recommended approach for curl and wget commands in multi-platform Dockerfiles

If we wanted to have a single Dockerfile build across (at minimum) x86-64, ARM 64-Bit, and ARM 32-Bit, we can use BuildKit with the TARGETPLATFORM argument to get a more consistent environment variable in our RUN commands, but it's not perfect. We'll still need to convert that output to what our RUN commands need.

TARGETPLATFORM is actually the combo of TARGETOS/TARGETARCH/TARGETVARIANT so in some cases you could use those to help the situation, but as you can see below, the arm/v6 vs arm/v7 vs arm/v8 output can make all this tricky. TARGETARCH is too general, and TARGETVARIANT may be blank (in the case of arm64).

So when I use docker buildx build --platform, what do I see inside the BuildKit environment?

Here's my results for this Dockerfile:

FROM busybox
ARG TARGETPLATFORM
ARG TARGETARCH
ARG TARGETVARIANT
RUN printf "I'm building for TARGETPLATFORM=${TARGETPLATFORM}" \
    && printf ", TARGETARCH=${TARGETARCH}" \
    && printf ", TARGETVARIANT=${TARGETVARIANT} \n" \
    && printf "With uname -s : " && uname -s \
    && printf "and  uname -m : " && uname -m

Here are the results when using the command docker buildx build --progress=plain --platform=<VALUE> .:

  1. --platform=linux/amd64 and --platform=linux/x86-64 and --platform=linux/x86_64

    I'm building for TARGETPLATFORM=linux/amd64, TARGETARCH=amd64, TARGETVARIANT=
    With uname -s : Linux
    and  uname -m : x86_64
    
  2. --platform=linux/arm64 and --platform=linux/arm64/v8 TARGETVARIANT is blank

    I'm building for TARGETPLATFORM=linux/arm64, TARGETARCH=arm64, TARGETVARIANT=
    With uname -s : Linux
    and  uname -m : aarch64
    
  3. --platform=linux/arm/v8 Don't use this. It builds but is inconsistent. I'd think this would be an alias to arm64, but it returns weird results (uname thinks it's 32bit, TARGETARCH is not arm64)

    I'm building for TARGETPLATFORM=linux/arm/v8, TARGETARCH=arm, TARGETVARIANT=v8
    With uname -s : Linux
    and  uname -m : armv7l
    
  4. --platform=linux/arm and --platform=linux/arm/v7 and --platform=linux/armhf

    I'm building for TARGETPLATFORM=linux/arm/v7, TARGETARCH=arm, TARGETVARIANT=v7
    With uname -s : Linux
    and  uname -m : armv7l
    
  5. --platform=linux/arm/v6 and --platform=linux/armel

    I'm building for TARGETPLATFORM=linux/arm/v6, TARGETARCH=arm, TARGETVARIANT=v6
    With uname -s : Linux
    and  uname -m : armv7l
    
  6. --platform=linux/i386 and --platform=linux/386

    I'm building for TARGETPLATFORM=linux/386, TARGETARCH=386, TARGETVARIANT=
    With uname -s : Linux
    and  uname -m : i686
    

So what then, how do we proceed?

Know what platforms you can build in your Docker Engine

First, you'll need to know what platforms your Docker Engine can build. Docker can support multi-platform builds with the buildx command. The README is great. By default it only supports the platform that Docker Engine (daemon) is running on, but if QEMU is installed, it can emulate many others. You can see the list it's currently enabled for with the docker buildx inspect --bootstrap command.

For example, this is what I see in Docker Desktop on a Intel-based Mac and a Windows 10 with WSL2, with linux/amd64 being the native platform, and the rest using QEMU emulation:

linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

I see the same list in Docker Desktop on a Apple M1 Mac, with linux/arm64 being the native platform, and the rest using QEMU emulation:

linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

This is what I see in Docker for Linux on a Raspberry Pi 4 with Raspbian (32bit as of early 2021). QEMU isn't enabled by default, so only the native options show up:

linux/arm/v7, linux/arm/v6

This is what I see in Docker for Linux on a Digital Ocean amd64 standard droplet. Notice again, QEMU isn't setup so the list is much shorter:

linux/amd64, linux/386

Add Dockerfile logic to detect the platform it needs to use

Let's use tini as an example of how to ensure that a single Dockerfile and download the correct tini build into our container image for Linux on amd64, arm64, arm/v7, arm/v6, and i386. We'll use a separate build-stage, evaluate the TARGETPLATFORM, and manually convert the value (via sh case statement) to what the specific binary URL needs.

This was inspired by @crazy-max in his docker-in-docker Dockerfile.

See the full Dockerfile here: example-tini\Dockerfile

FROM --platform=${BUILDPLATFORM} alpine as tini-binary
ENV TINI_VERSION=v0.19.0
ARG TARGETPLATFORM
RUN case ${TARGETPLATFORM} in \
         "linux/amd64")  TINI_ARCH=amd64  ;; \
         "linux/arm64")  TINI_ARCH=arm64  ;; \
         "linux/arm/v7") TINI_ARCH=armhf  ;; \
         "linux/arm/v6") TINI_ARCH=armel  ;; \
         "linux/386")    TINI_ARCH=i386   ;; \
    esac \
 && wget -q https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TINI_ARCH} -O /tini \
 && chmod +x /tini

Further Reading

Docker Blog from Adrian Mouat on multi-platform Docker builds.

MORE TO COME, WIP

  • Background on manifests, multi-architecture repos
  • Using third-party tools like regctl to make your life easier (i.e. regctl image manifest --list golang)
  • Breakdown the three parts of the platform ARG better

More Repositories

1

udemy-docker-mastery

Docker Mastery Udemy course to build, compose, deploy, and manage containers from local development to high-availability in the cloud
JavaScript
5,263
star
2

node-docker-good-defaults

sample node app for Docker examples
JavaScript
2,323
star
3

dogvscat

Sample Docker Swarm cluster stack of tools
HCL
504
star
4

nodejs-rocks-in-docker

DockerCon "Docker for Node.js" examples
Dockerfile
494
star
5

docker-mastery-for-nodejs

Docker Mastery for Node.js Projects, From a Docker Captain
JavaScript
412
star
6

awesome-swarm

All the awesome tools, docs, and training on Docker and Mirantis Swarm Mode (SwarmKit)
385
star
7

php-docker-good-defaults

*WORK IN PROGRESS* sample PHP/Laravel app for Docker examples
Dockerfile
362
star
8

docker-vackup

Script to easily backup and restore docker volumes
Shell
362
star
9

jekyll-serve

Jekyll in a Docker Container For Easy SSG Development
Dockerfile
336
star
10

docker-ci-automation

GitHub Actions automation examples with Docker's official actions
Shell
247
star
11

kubernetes-mastery

Kubernetes course on Udemy from @BretFisher and @jpetazzo
Shell
233
star
12

compose-dev-tls

Easy Traefik TLS proxy plus certificate generation for Docker Compose local development use
Shell
111
star
13

github-actions-templates

Reusable GitHub Actions workflow examples for cloud native DevOps
Dockerfile
75
star
14

podspec

Kubernetes Pod Specification Good Defaults
71
star
15

browncoat

Container for testing app failures in orchestrators. It aims to misbehave.
JavaScript
69
star
16

httping-docker

Ping with HTTP requests, built directly from master
C
52
star
17

docker-build-workflow

A Reusable Workflow of the Docker GitHub Actions
Dockerfile
42
star
18

super-linter-workflow

A Reusable Workflow of the Super-Linter GitHub Action
37
star
19

gha-runners

Terraform to create GitHub Action self-hosted runners in EC2 using ASG
HCL
29
star
20

slack-signup

Slack Team Access Request Form in Meteor
JavaScript
27
star
21

container-security-steps

Docker and Kubernetes security steps to help you create, build, test, and run safer in containers
26
star
22

stress

Docker images for stressing cpu and memory
Dockerfile
25
star
23

sysbench-docker-hpe

Sysbench Dockerfiles and Scripts for VM and Container benchmarking MySQL
Shell
19
star
24

BretFisher

18
star
25

petclinic

sample dockerization of a Java Spring Boot app
Java
17
star
26

redis-tini

Docker image of official redis with tini added for liveness healthchecks that reap zombies
Dockerfile
17
star
27

nodemongoapp

Docker Example Node + Mongo App
JavaScript
11
star
28

initcontainers

Kubernetes initContainers examples and good defaults
10
star
29

bret.run

Static files for easy gettin' stuff
Shell
7
star
30

PowerShell-Profile

Microsoft.PowerShell_profile.ps1
PowerShell
7
star
31

resume

My Resume, hopefully current.
HTML
5
star
32

gitops-argocd

4
star
33

completion-image

Generates completion certificates (images) for students based on a template image and a CSV file containing student names.
Go
3
star
34

wordsmith-k8s

Manifests for Docker's wordsmith demo
3
star
35

cheese

Simple web servers of cheese for yummy demos.
HTML
3
star
36

docker-github-actions-monorepo-example

3
star
37

slide-to-image

JavaScript
1
star
38

.github

1
star
39

dotfiles

My public dotfiles for Codespaces and examples
Shell
1
star
40

gatsby-starter-netlify-cms

JavaScript
1
star
41

nodeBeginnerBookLearnings

Me just learning the node code from the Excellent
1
star
42

AutoSPInstaller-User-Creator

I use this PowerShell Script with www.autospinstaller.com to automate (most) of a SharePoint 2010 deployment
PowerShell
1
star
43

katacoda-scenarios

https://www.katacoda.com/bretfisher
1
star
44

pmx-docker

Shell
1
star