1. buildx

buildx is a plugin for docker. The docker buildx subcommand executes the buildx binary. This article analyzes how to compile buildx yourself.

2. Standard Build

The official documentation provides the standard build method: https://github.com/docker/buildx#building

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Buildx 0.6+
$ docker buildx bake "https://github.com/docker/buildx.git"
$ mkdir -p ~/.docker/cli-plugins
$ mv ./bin/build/buildx ~/.docker/cli-plugins/docker-buildx

# Docker 19.03+
$ DOCKER_BUILDKIT=1 docker build --platform=local -o . "https://github.com/docker/buildx.git"
$ mkdir -p ~/.docker/cli-plugins
$ mv buildx ~/.docker/cli-plugins/docker-buildx

# Local 
$ git clone https://github.com/docker/buildx.git && cd buildx
$ make install

3. Compilation flow analysis

The following is an analysis of the compilation process using the make binaries command as an entry point.

Execute docker buildx bake binaries to compile.

https://github.com/docker/buildx/blob/v0.10.2/Makefile#L22-L24

1
2
3
4
5
6
7
...
export BUILDX_CMD ?= docker buildx
...
.PHONY: binaries
binaries:
    $(BUILDX_CMD) bake binaries
...

The configuration of bake is as follows, setting the target to the binaries stage and outputting the compiled file to the ./bin/build directory.

https://github.com/docker/buildx/blob/v0.10.2/docker-bake.hcl#L101-L106

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
variable "DESTDIR" {
  default = "./bin"
}
...
target "binaries" {
  inherits = ["_common"]
  target = "binaries"
  output = ["${DESTDIR}/build"]
  platforms = ["local"]
}
...

Call hack/build compilation in the buildx-build phase.

https://github.com/docker/buildx/blob/v0.10.2/Dockerfile#L60

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# syntax=docker/dockerfile-upstream:1.5.0

ARG GO_VERSION=1.19
ARG XX_VERSION=1.1.2
ARG DOCKERD_VERSION=20.10.14

FROM docker:$DOCKERD_VERSION AS dockerd-release

# xx is a helper for cross-compilation
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx

FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest

FROM golatest AS gobase
COPY --from=xx / /
RUN apk add --no-cache file git
ENV GOFLAGS=-mod=vendor
ENV CGO_ENABLED=0
WORKDIR /src

FROM gobase AS buildx-version
RUN --mount=type=bind,target=. <<EOT
  set -e
  mkdir /buildx-version
  echo -n "$(./hack/git-meta version)" | tee /buildx-version/version
  echo -n "$(./hack/git-meta revision)" | tee /buildx-version/revision
EOT

FROM gobase AS buildx-build
ARG TARGETPLATFORM
RUN --mount=type=bind,target=. \
  --mount=type=cache,target=/root/.cache \
  --mount=type=cache,target=/go/pkg/mod \
  --mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
  set -e
  xx-go --wrap
  DESTDIR=/usr/bin VERSION=$(cat /buildx-version/version) REVISION=$(cat /buildx-version/revision) GO_EXTRA_LDFLAGS="-s -w" ./hack/build
  xx-verify --static /usr/bin/docker-buildx
EOT

...

FROM scratch AS binaries-unix
COPY --link --from=buildx-build /usr/bin/docker-buildx /buildx

...

FROM binaries-unix AS binaries-linux

...

FROM binaries-$TARGETOS AS binaries
# enable scanning for this stage
ARG BUILDKIT_SBOM_SCAN_STAGE=true

...

hack/build calls go build.

https://github.com/docker/buildx/blob/v0.10.2/hack/build

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env sh

set -e

: "${DESTDIR=./bin/build}"
: "${PACKAGE=github.com/docker/buildx}"
: "${VERSION=$(./hack/git-meta version)}"
: "${REVISION=$(./hack/git-meta revision)}"

: "${CGO_ENABLED=0}"
: "${GO_PKG=github.com/docker/buildx}"
: "${GO_LDFLAGS=-X ${GO_PKG}/version.Version=${VERSION} -X ${GO_PKG}/version.Revision=${REVISION} -X ${GO_PKG}/version.Package=${PACKAGE}}"
: "${GO_EXTRA_LDFLAGS=}"

set -x
CGO_ENABLED=$CGO_ENABLED go build -mod vendor -trimpath -ldflags "${GO_LDFLAGS} ${GO_EXTRA_LDFLAGS}" -o "${DESTDIR}/docker-buildx" ./cmd/buildx

4. Custom

The final build command is located in the hack/build file.

https://github.com/docker/buildx/blob/v0.10.2/hack/build#L16

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env sh

set -e

: "${DESTDIR=./bin/build}"
: "${PACKAGE=github.com/docker/buildx}"
: "${VERSION=$(./hack/git-meta version)}"
: "${REVISION=$(./hack/git-meta revision)}"

: "${CGO_ENABLED=0}"
: "${GO_PKG=github.com/docker/buildx}"
: "${GO_LDFLAGS=-X ${GO_PKG}/version.Version=${VERSION} -X ${GO_PKG}/version.Revision=${REVISION} -X ${GO_PKG}/version.Package=${PACKAGE}}"
: "${GO_EXTRA_LDFLAGS=}"

set -x
CGO_ENABLED=$CGO_ENABLED go build -mod vendor -trimpath -ldflags "${GO_LDFLAGS} ${GO_EXTRA_LDFLAGS}" -o "${DESTDIR}/docker-buildx" ./cmd/buildx

4. Disable compilation optimization and inlining

1
2
3
4
5
6
7
8
9
$ sed -i 's/go build/go build -gcflags="all=-N -l"/g' hack/build
$ sed -i 's/${GO_EXTRA_LDFLAGS}//g' hack/build
$ make binaries
docker buildx bake binaries
[+] Building 7.3s (21/21) FINISHED
...
 => => copying files 76.04MB
$ ls -lah bin/build/buildx 
-rwxr-xr-x 1 root root 73M Feb 14 12:52 bin/build/buildx