Repository navigation
docker CLI: build the Windows arm64 docker.exe upstream does not ship (#450) - #460
Merged
Merged
Conversation
…#450) The build and the release plumbing. The manifest still carries arm64 as a placeholder -- that entry needs a published dockercli- release to point at, which needs this on main first, the same order #388 followed. Upstream publishes nothing here: download.docker.com's static tree has exactly one directory, x86_64/, and docker/cli attaches no release assets at all. So `skrog cli install` on Windows on ARM laid down compose, buildx and the credential helper -- all of which DO have real arm64 builds -- and skipped the one command anybody types. third_party/docker-cli/ builds it: docker/cli at a tag pinned to its dereferenced commit, in the pinned Go toolchain the engine uses, cross compiled with CGO off. Cross-compiling is not a compromise here the way it would be for the engine -- this is `go build` with GOARCH set against fully vendored pure Go, not a C-linked tree that needs boot-testing. Two details that cost time to find: - docker/cli ships vendor.mod and vendor.sum rather than go.mod and go.sum, deliberately, so it cannot be consumed as a library. Their own scripts rename them; so does this. - The upstream ldflags set PlatformName to "Docker Engine - Community". It is left UNSET here, and that is the one thing in this change that is about honesty rather than mechanics: this is not Docker's build, and stamping their build string on it would be the single genuinely misleading thing the script could do. Version and commit are set, so `docker version` reports something true rather than "dev". The PE machine type is asserted twice -- in build.sh and again in the workflow, outside it. A cross-compile that silently produced x86-64 would pass every other check and fail only on a user's ARM machine, which is the exact failure #388 was about, and it is the one thing a build on an x86-64 runner cannot feel. Verified against a real local build: PE offset 128, machine bytes 64aa, 28.3 MB. amd64 stays Docker's own bytes. Anyone can re-download that zip and check it against the pinned digest without reference to Skrog; a binary we build can only be checked against us, and building it too would put every user behind our build rather than only the arm64 users with no alternative. It is not about code signing -- Docker's published Windows CLI is NotSigned, measured on the binary `skrog cli install` places. A test fails if the amd64 URL ever stops pointing at download.docker.com. This amends the "ship CLI binaries unmodified" line in PLAN §09, which #450 asked be decided rather than slid past. Building from Apache-2.0 source in our own CI is the treatment that same line already prescribes for the engine, and it is the opposite of repackaging Docker's distribution. Also closes a hole a third tag namespace opens. release.yml and rootfs.yml excluded each other by name -- "not rootfs-" -- which is an open list: a dockercli- tag would have attached skrog.exe to a docker CLI release because nothing said no. All three guards now test for their own prefix positively, which the next namespace cannot break. rootfs.yml's build job also stops rebuilding the engine, twice, for every release that is not its own. CI's shellcheck ran a hardcoded list of paths; it now runs every .sh in the tree, matching what scripts/lint.ps1 already did locally.
`chmod +x` on a Windows working tree does not reach the index, so it landed as 100644 and the workflow died with "Permission denied" (exit 126) before running a line of it. guest/rootfs/build.sh is 100755, which is why nothing else noticed. `git update-index --chmod=+x`, which is the part that actually records it.
Not a change to the build -- a fact about it that is worth writing down, because it is the strongest answer to "why should I trust a binary you built". The output has no timestamp and no build host in it (BuildTime is deliberately unset), so the same commit produces the same bytes anywhere. Confirmed rather than assumed: my local build and this PR's CI build of docker/cli 4a63305d both came out at 910087c0d9a80ac0e7802f8c48b98cdc7fd890c78e0dab5f0472bee856ac50a0 28,319,232 bytes Different machines, different kernels, same binary. The SLSA attestation says we built it; reproducibility means nobody has to take our word for what from.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The build and the release plumbing for #450. The manifest still carries arm64 as a placeholder — that entry needs a published
dockercli-release to point at, which needs this onmainfirst. Same order #388 followed.The gap
Upstream publishes nothing here.
download.docker.com's static tree has exactly one directory,x86_64/;docker/cliattaches no release assets at all. Soskrog cli installon Windows on ARM laid down compose, buildx and the credential helper — all of which do have real arm64 builds — and skipped the one command anybody types.The build
third_party/docker-cli/—docker/cliat a tag pinned to its dereferenced commit, in the same pinned Go toolchain as the engine,CGO_ENABLED=0.Cross-compiled, and that is not a compromise the way it would be for the engine: this is
go buildwithGOARCHset against a fully vendored pure-Go tree, not a C-linked one that needs boot-testing on native hardware.Two details that cost time:
docker/clishipsvendor.mod/vendor.sum, notgo.mod/go.sum— deliberately, so the repo cannot be consumed as a library. Their own scripts rename them; so does this.PlatformNameis left unset. Upstream's ldflags stamp itDocker Engine - Community. This is not Docker's build, and putting their build string on it would be the one genuinely misleading thing this script could do. Version and commit are set, sodocker versionreports something true rather thandev.The architecture is asserted twice
In
build.sh, and again in the workflow outside it:A cross-compile that silently produced x86-64 would pass every other check here and fail only on a user's ARM machine — the exact failure #388 was about, and the one thing a build on an x86-64 runner cannot feel. The second check exists so a bug in the script's own parsing can't pass its own test.
Verified against a real local build before writing any of the CI: PE offset 128, machine bytes
64aa, 28.3 MB, from commit4a63305d.amd64 stays Docker's
Deliberate, and now enforced by a test. Those bytes are verifiable by anyone against the pinned digest with no reference to Skrog; a binary we build can only be checked against us; and building it too would put every user behind our build rather than only the arm64 users who have no alternative.
Not about code signing — Docker's published Windows CLI is
NotSignedtoo, measured on the binaryskrog cli installplaces. There's no signature being preserved on one side and lost on the other.docs/docker-cli.mdgets a table of where each of the four binaries comes from, the reasoning, and the exit condition: when upstream ships an arm64 build, both point at Docker again andthird_party/docker-cli/is deleted.Policy
#450 asked that the "ship CLI binaries unmodified" line in PLAN §09 be decided rather than slid past. It is amended in this PR, in place, dated: building from Apache-2.0 source in our own CI is the treatment that same line already prescribes for the engine, and it is the opposite of the repackaging the trademark concern is about — nothing is redistributed from Docker's channels and the binary carries no Docker build string.
A hole a third tag namespace opens
release.ymlandrootfs.ymlexcluded each other by name —!startsWith(tag, 'rootfs-'). That is an open list: adockercli-tag would have attachedskrog.exeto a docker CLI release, because nothing said no.All three guards now test for their own prefix positively, which the next namespace cannot break.
rootfs.yml's build job also stops rebuilding the engine — twice, ~6 min each — for every release that isn't its own.CI's shellcheck ran a hardcoded path list; it now runs every
.shin the tree, matching whatscripts/lint.ps1already did locally.Verified
go test ./...,go vetacross all build tags,gofmt,govulncheck,shellcheck,scripts/lint.ps1, docs rebuild,check-links.ps1. The arm64 build itself runs on this PR —dockercli.ymltriggers on any PR touchingthird_party/docker-cli/**.Next
Cut
dockercli-v29.8.1, then a one-line PR adds the URL and checksum tointernal/dockercli/manifest.jsonand Windows on ARM has a complete toolchain.