diff --git a/docs/deployment-and-usage.md b/docs/deployment-and-usage.md index d82d8fd..ae83a67 100644 --- a/docs/deployment-and-usage.md +++ b/docs/deployment-and-usage.md @@ -263,24 +263,24 @@ control consumer, client consumer, E2E, then image publication. ### Reproducible multi-platform Buildx lifecycle The named Buildx builders are local acceleration/cache only; they are not a -deployment dependency and can be removed after publication. To create a fresh -builder, verify its platforms, publish a release, and remove it afterwards: +deployment dependency and can be removed after publication. Use the checked-in +publisher: it installs only the non-native binfmt handler, creates a temporary +rootless BuildKit builder, verifies both platforms, publishes the index, +displays its digest, then removes both temporary resources. ```bash -docker buildx create --name archive-control-release --driver docker-container --use -docker buildx inspect --bootstrap -docker buildx build --platform linux/amd64,linux/arm64 \ - --tag sodium/archive-clients:vX.Y.Z --push . -docker buildx rm archive-control-release +scripts/publish-image.sh vX.Y.Z +scripts/publish-image.sh vX.Y.Z --also-latest ``` -`docker buildx inspect` must list both `linux/amd64` and `linux/arm64` before -publishing. If the host has no arm64 emulation, install/configure it according -to the host Docker distribution before the build; do not publish a partial -single-platform tag. Retain the pushed manifest digest in the release notes -and deploy the immutable tag or digest. The optional `archive-control-qemu` -builder follows the same lifecycle when it is used for an emulation smoke -build. +The publisher deliberately uses `moby/buildkit:rootless` with +`--oci-worker-no-process-sandbox`. On nested Docker hosts, the default OCI +sandbox can fail while masking `/proc/acpi` for an emulated build; rootless +BuildKit confines that compatibility setting to the disposable builder. It +refuses to publish unless `docker buildx inspect` reports both `linux/amd64` +and `linux/arm64`, and removes the builder and binfmt handler on success, +failure, or interruption. Retain the displayed manifest digest in release +notes and deploy the immutable tag or digest. ## Operator usage diff --git a/scripts/publish-image.sh b/scripts/publish-image.sh new file mode 100755 index 0000000..23e96ab --- /dev/null +++ b/scripts/publish-image.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Publish one complete Archive Control client OCI image index. +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: scripts/publish-image.sh [--also-latest] + +Builds and pushes sodium/archive-clients for linux/amd64 and linux/arm64. +Docker must already be authenticated to the target registry. +EOF +} + +if [[ $# -lt 1 || ${1:-} == '-h' || ${1:-} == '--help' ]]; then + usage + exit 2 +fi + +tag=$1 +shift +also_latest=false +if [[ ${1:-} == '--also-latest' ]]; then + also_latest=true + shift +fi +if [[ $# -ne 0 ]]; then + usage + exit 2 +fi + +repository=${IMAGE_REPOSITORY:-sodium/archive-clients} +builder=${BUILDER:-archive-control-release} +buildkit_image=${BUILDKIT_IMAGE:-moby/buildkit:rootless} +# Nested Docker hosts can reject default OCI /proc mount masking. Rootless +# BuildKit confines this compatibility flag to the disposable release builder. +buildkitd_flags=${BUILDKITD_FLAGS:---oci-worker-no-process-sandbox} +binfmt_image=${BINFMT_IMAGE:-tonistiigi/binfmt} +host_arch=$(docker version --format '{{.Server.Arch}}') +case ${host_arch} in + arm64|aarch64) emulated_arch=amd64 ;; + amd64|x86_64) emulated_arch=arm64 ;; + *) echo "Unsupported Docker server architecture: ${host_arch}" >&2; exit 1 ;; +esac + +builder_created=false +binfmt_installed=false +cleanup() { + local status=$? + trap - EXIT + if [[ ${builder_created} == true ]]; then + docker buildx rm "${builder}" >/dev/null 2>&1 || true + fi + if [[ ${binfmt_installed} == true ]]; then + docker run --privileged --rm "${binfmt_image}" --uninstall "${emulated_arch}" \ + >/dev/null 2>&1 || true + fi + exit "${status}" +} +trap cleanup EXIT + +docker run --privileged --rm "${binfmt_image}" --install "${emulated_arch}" \ + >/dev/null +binfmt_installed=true +if docker buildx inspect "${builder}" >/dev/null 2>&1; then + docker buildx rm "${builder}" >/dev/null +fi +docker buildx create --name "${builder}" --driver docker-container \ + --driver-opt "image=${buildkit_image}" \ + --buildkitd-flags "${buildkitd_flags}" --use >/dev/null +builder_created=true + +platforms=$(docker buildx inspect "${builder}" --bootstrap 2>&1) +for platform in linux/amd64 linux/arm64; do + if ! grep -Fq "${platform}" <<<"${platforms}"; then + echo "Builder ${builder} does not support ${platform}; refusing partial release." >&2 + exit 1 + fi +done + +tags=(--tag "${repository}:${tag}") +if [[ ${also_latest} == true ]]; then + tags+=(--tag "${repository}:latest") +fi +docker buildx build --pull --platform linux/amd64,linux/arm64 \ + --push "${tags[@]}" . +docker buildx imagetools inspect "${repository}:${tag}"