Skip to content

Instantly share code, notes, and snippets.

@stuft2
Last active March 19, 2026 17:19
Show Gist options
  • Select an option

  • Save stuft2/0df80e139d965bdab8652542d5d2d595 to your computer and use it in GitHub Desktop.

Select an option

Save stuft2/0df80e139d965bdab8652542d5d2d595 to your computer and use it in GitHub Desktop.
Mirror a multiarch image from docker hub to quay.byu.edu
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
scripts/mirror-multiarch-image.sh [source-image] [destination-image]
Defaults:
source-image docker.io/library/golang:1.26
destination-image quay.byu.edu/docker-cache/library/golang:1.26
Environment:
PLATFORM_FILTER jq expression applied to each manifest's .platform object.
Default keeps Linux platform manifests and excludes
attestation manifests with unknown platform:
.os == "linux" and .architecture != "unknown"
TMP_TAG_PREFIX Prefix for intermediate tags pushed to the destination repo.
Default: mirror-tmp
Prerequisites:
- docker buildx
- jq
- skopeo
- authenticated access to the destination registry
Example:
scripts/mirror-multiarch-image.sh
Linux-only example:
PLATFORM_FILTER='.os == "linux" and .architecture != "unknown"' \
scripts/mirror-multiarch-image.sh
All non-attestation platforms example:
PLATFORM_FILTER='.os != "unknown" and .architecture != "unknown"' \
scripts/mirror-multiarch-image.sh
EOF
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "missing required command: $1" >&2
exit 1
fi
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
require_cmd docker
require_cmd jq
require_cmd skopeo
SRC_IMAGE="${1:-docker.io/library/golang:1.26}"
DST_IMAGE="${2:-quay.byu.edu/docker-cache/library/golang:1.26}"
PLATFORM_FILTER="${PLATFORM_FILTER:-.os == \"linux\" and .architecture != \"unknown\"}"
TMP_TAG_PREFIX="${TMP_TAG_PREFIX:-mirror-tmp}"
STAMP="$(date +%Y%m%d%H%M%S)"
if [[ "${SRC_IMAGE}" == *@* ]]; then
echo "source image must not include a digest: ${SRC_IMAGE}" >&2
exit 1
fi
if [[ "${DST_IMAGE}" != *:* ]]; then
echo "destination image must include a tag: ${DST_IMAGE}" >&2
exit 1
fi
SRC_REPO="${SRC_IMAGE%:*}"
DST_REPO="${DST_IMAGE%:*}"
DST_TAG="${DST_IMAGE##*:}"
TMP_REFS=()
cleanup_tmp_refs() {
if [[ "${#TMP_REFS[@]}" -eq 0 ]]; then
return
fi
echo "Deleting temporary tags" >&2
for tmp_ref in "${TMP_REFS[@]}"; do
echo "Deleting ${tmp_ref}" >&2
skopeo delete "docker://${tmp_ref}" || true
done
}
trap cleanup_tmp_refs EXIT
echo "Inspecting source manifest list: ${SRC_IMAGE}" >&2
mapfile -t MANIFEST_ROWS < <(
docker buildx imagetools inspect --raw "${SRC_IMAGE}" | jq -r "
.manifests[]
| select(.platform != null)
| select(.platform | (${PLATFORM_FILTER}))
| [
.digest,
.platform.os,
.platform.architecture,
(.platform.variant // \"\"),
(.platform[\"os.version\"] // \"\")
]
| @tsv
"
)
if [[ "${#MANIFEST_ROWS[@]}" -eq 0 ]]; then
echo "no platform manifests matched filter: ${PLATFORM_FILTER}" >&2
exit 1
fi
for row in "${MANIFEST_ROWS[@]}"; do
IFS=$'\t' read -r digest os arch variant os_version <<<"${row}"
digest_short="${digest#sha256:}"
digest_short="${digest_short:0:12}"
platform="${os}/${arch}"
if [[ -n "${variant}" ]]; then
platform="${platform}/${variant}"
fi
if [[ -n "${os_version}" ]]; then
platform="${platform} os.version=${os_version}"
fi
tmp_tag="${TMP_TAG_PREFIX}-${DST_TAG}-${STAMP}-${digest_short}"
tmp_ref="${DST_REPO}:${tmp_tag}"
echo "Copying ${platform} (${digest}) -> ${tmp_ref}" >&2
skopeo copy \
"docker://${SRC_REPO}@${digest}" \
"docker://${tmp_ref}"
TMP_REFS+=("${tmp_ref}")
done
echo "Creating manifest list: ${DST_IMAGE}" >&2
docker buildx imagetools create \
-t "${DST_IMAGE}" \
"${TMP_REFS[@]}"
echo "Inspecting destination manifest list: ${DST_IMAGE}" >&2
docker buildx imagetools inspect "${DST_IMAGE}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment