committed by
GitHub
11 changed files with 181 additions and 112 deletions
@ -0,0 +1,33 @@ |
|||||
|
# The cross-built images have the build arch (`amd64`) embedded in the image |
||||
|
# manifest, rather than the target arch. For example: |
||||
|
# |
||||
|
# $ docker inspect bitwardenrs/server:latest-armv7 | jq -r '.[]|.Architecture' |
||||
|
# amd64 |
||||
|
# |
||||
|
# Recent versions of Docker have started printing a warning when the image's |
||||
|
# claimed arch doesn't match the host arch. For example: |
||||
|
# |
||||
|
# WARNING: The requested image's platform (linux/amd64) does not match the |
||||
|
# detected host platform (linux/arm/v7) and no specific platform was requested |
||||
|
# |
||||
|
# The image still works fine, but the spurious warning creates confusion. |
||||
|
# |
||||
|
# Docker doesn't seem to provide a way to directly set the arch of an image |
||||
|
# at build time. To resolve the build vs. target arch discrepancy, we use |
||||
|
# Docker Buildx to build a new set of images with the correct target arch. |
||||
|
# |
||||
|
# Docker Buildx uses this Dockerfile to build an image for each requested |
||||
|
# platform. Since the Dockerfile basically consists of a single `FROM` |
||||
|
# instruction, we're effectively telling Buildx to build a platform-specific |
||||
|
# image by simply copying the existing cross-built image and setting the |
||||
|
# correct target arch as a side effect. |
||||
|
# |
||||
|
# References: |
||||
|
# |
||||
|
# - https://docs.docker.com/buildx/working-with-buildx/#build-multi-platform-images |
||||
|
# - https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope |
||||
|
# - https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact |
||||
|
# |
||||
|
ARG LOCAL_REPO |
||||
|
ARG DOCKER_TAG |
||||
|
FROM ${LOCAL_REPO}:${DOCKER_TAG}-${TARGETARCH}${TARGETVARIANT} |
@ -1,19 +1,16 @@ |
|||||
# The default Debian-based images support these arches for all database connections |
# The default Debian-based images support these arches for all database backends. |
||||
# |
|
||||
# Other images (Alpine-based) currently |
|
||||
# support only a subset of these. |
|
||||
arches=( |
arches=( |
||||
amd64 |
amd64 |
||||
arm32v6 |
armv6 |
||||
arm32v7 |
armv7 |
||||
arm64v8 |
arm64 |
||||
) |
) |
||||
|
|
||||
if [[ "${DOCKER_TAG}" == *alpine ]]; then |
if [[ "${DOCKER_TAG}" == *alpine ]]; then |
||||
# The Alpine build currently only works for amd64. |
# The Alpine image build currently only works for certain arches. |
||||
os_suffix=.alpine |
distro_suffix=.alpine |
||||
arches=( |
arches=( |
||||
amd64 |
amd64 |
||||
arm32v7 |
armv7 |
||||
) |
) |
||||
fi |
fi |
||||
|
@ -0,0 +1,18 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
set -ex |
||||
|
|
||||
|
# Print some environment info in case it's useful for troubleshooting. |
||||
|
id |
||||
|
pwd |
||||
|
df -h |
||||
|
env |
||||
|
docker info |
||||
|
docker version |
||||
|
|
||||
|
# Install build dependencies. |
||||
|
deps=( |
||||
|
jq |
||||
|
) |
||||
|
apt-get update |
||||
|
apt-get install -y "${deps[@]}" |
@ -1,117 +1,138 @@ |
|||||
#!/bin/bash |
#!/bin/bash |
||||
|
|
||||
echo ">>> Pushing images..." |
source ./hooks/arches.sh |
||||
|
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled |
export DOCKER_CLI_EXPERIMENTAL=enabled |
||||
|
|
||||
declare -A annotations=( |
# Join a list of args with a single char. |
||||
[amd64]="--os linux --arch amd64" |
# Ref: https://stackoverflow.com/a/17841619 |
||||
[arm32v6]="--os linux --arch arm --variant v6" |
join() { local IFS="$1"; shift; echo "$*"; } |
||||
[arm32v7]="--os linux --arch arm --variant v7" |
|
||||
[arm64v8]="--os linux --arch arm64 --variant v8" |
|
||||
) |
|
||||
|
|
||||
source ./hooks/arches.sh |
|
||||
|
|
||||
set -ex |
set -ex |
||||
|
|
||||
declare -A images |
echo ">>> Starting local Docker registry..." |
||||
|
|
||||
|
# Docker Buildx's `docker-container` driver is needed for multi-platform |
||||
|
# builds, but it can't access existing images on the Docker host (like the |
||||
|
# cross-compiled ones we just built). Those images first need to be pushed to |
||||
|
# a registry -- Docker Hub could be used, but since it's not trivial to clean |
||||
|
# up those intermediate images on Docker Hub, it's easier to just run a local |
||||
|
# Docker registry, which gets cleaned up automatically once the build job ends. |
||||
|
# |
||||
|
# https://docs.docker.com/registry/deploying/ |
||||
|
# https://hub.docker.com/_/registry |
||||
|
# |
||||
|
# Use host networking so the buildx container can access the registry via |
||||
|
# localhost. |
||||
|
# |
||||
|
docker run -d --name registry --network host registry:2 # defaults to port 5000 |
||||
|
|
||||
|
# Docker Hub sets a `DOCKER_REPO` env var with the format `index.docker.io/user/repo`. |
||||
|
# Strip the registry portion to construct a local repo path for use in `Dockerfile.buildx`. |
||||
|
LOCAL_REGISTRY="localhost:5000" |
||||
|
REPO="${DOCKER_REPO#*/}" |
||||
|
LOCAL_REPO="${LOCAL_REGISTRY}/${REPO}" |
||||
|
|
||||
|
echo ">>> Pushing images to local registry..." |
||||
|
|
||||
for arch in ${arches[@]}; do |
for arch in ${arches[@]}; do |
||||
images[$arch]="${DOCKER_REPO}:${DOCKER_TAG}-${arch}" |
docker_image="${DOCKER_REPO}:${DOCKER_TAG}-${arch}" |
||||
|
local_image="${LOCAL_REPO}:${DOCKER_TAG}-${arch}" |
||||
|
docker tag "${docker_image}" "${local_image}" |
||||
|
docker push "${local_image}" |
||||
done |
done |
||||
|
|
||||
# Push the images that were just built; manifest list creation fails if the |
echo ">>> Setting up Docker Buildx..." |
||||
# images (manifests) referenced don't already exist in the Docker registry. |
|
||||
for image in "${images[@]}"; do |
# Same as earlier, use host networking so the buildx container can access the |
||||
docker push "${image}" |
# registry via localhost. |
||||
done |
# |
||||
|
# Ref: https://github.com/docker/buildx/issues/94#issuecomment-534367714 |
||||
|
# |
||||
|
docker buildx create --name builder --use --driver-opt network=host |
||||
|
|
||||
manifest_lists=("${DOCKER_REPO}:${DOCKER_TAG}") |
echo ">>> Running Docker Buildx..." |
||||
|
|
||||
# If the Docker tag starts with a version number, assume the latest release is |
tags=("${DOCKER_REPO}:${DOCKER_TAG}") |
||||
# being pushed. Add an extra manifest (`latest` or `alpine`, as appropriate) |
|
||||
|
# If the Docker tag starts with a version number, assume the latest release |
||||
|
# is being pushed. Add an extra tag (`latest` or `alpine`, as appropriate) |
||||
# to make it easier for users to track the latest release. |
# to make it easier for users to track the latest release. |
||||
if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then |
if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then |
||||
if [[ "${DOCKER_TAG}" == *alpine ]]; then |
if [[ "${DOCKER_TAG}" == *alpine ]]; then |
||||
manifest_lists+=(${DOCKER_REPO}:alpine) |
tags+=(${DOCKER_REPO}:alpine) |
||||
else |
else |
||||
manifest_lists+=(${DOCKER_REPO}:latest) |
tags+=(${DOCKER_REPO}:latest) |
||||
|
|
||||
# Add an extra `latest-arm32v6` tag; Docker can't seem to properly |
|
||||
# auto-select that image on Armv6 platforms like Raspberry Pi 1 and Zero |
|
||||
# (https://github.com/moby/moby/issues/41017). |
|
||||
# |
|
||||
# Add this tag only for the SQLite image, as the MySQL and PostgreSQL |
|
||||
# builds don't currently work on non-amd64 arches. |
|
||||
# |
|
||||
# TODO: Also add an `alpine-arm32v6` tag if multi-arch support for |
|
||||
# Alpine-based bitwarden_rs images is implemented before this Docker |
|
||||
# issue is fixed. |
|
||||
if [[ ${DOCKER_REPO} == *server ]]; then |
|
||||
docker tag "${DOCKER_REPO}:${DOCKER_TAG}-arm32v6" "${DOCKER_REPO}:latest-arm32v6" |
|
||||
docker push "${DOCKER_REPO}:latest-arm32v6" |
|
||||
fi |
|
||||
fi |
fi |
||||
fi |
fi |
||||
|
|
||||
for manifest_list in "${manifest_lists[@]}"; do |
tag_args=() |
||||
# Create the (multi-arch) manifest list of arch-specific images. |
for tag in "${tags[@]}"; do |
||||
docker manifest create ${manifest_list} ${images[@]} |
tag_args+=(--tag "${tag}") |
||||
|
|
||||
# Make sure each image manifest is annotated with the correct arch info. |
|
||||
# Docker does not auto-detect the arch of each cross-compiled image, so |
|
||||
# everything would appear as `linux/amd64` otherwise. |
|
||||
for arch in "${arches[@]}"; do |
|
||||
docker manifest annotate ${annotations[$arch]} ${manifest_list} ${images[$arch]} |
|
||||
done |
|
||||
|
|
||||
# Push the manifest list. |
|
||||
docker manifest push --purge ${manifest_list} |
|
||||
done |
done |
||||
|
|
||||
# Avoid logging credentials and tokens. |
# Docker Buildx takes a list of target platforms (OS/arch/variant), so map |
||||
set +ex |
# the arch list to a platform list (assuming the OS is always `linux`). |
||||
|
declare -A arch_to_platform=( |
||||
# Delete the arch-specific tags, if credentials for doing so are available. |
[amd64]="linux/amd64" |
||||
# Note that `DOCKER_PASSWORD` must be the actual user password. Passing a JWT |
[armv6]="linux/arm/v6" |
||||
# obtained using a personal access token results in a 403 error with |
[armv7]="linux/arm/v7" |
||||
# {"detail": "access to the resource is forbidden with personal access token"} |
[arm64]="linux/arm64" |
||||
if [[ -z "${DOCKER_USERNAME}" || -z "${DOCKER_PASSWORD}" ]]; then |
) |
||||
exit 0 |
platforms=() |
||||
fi |
|
||||
|
|
||||
# Given a JSON input on stdin, extract the string value associated with the |
|
||||
# specified key. This avoids an extra dependency on a tool like `jq`. |
|
||||
extract() { |
|
||||
local key="$1" |
|
||||
# Extract "<key>":"<val>" (assumes key/val won't contain double quotes). |
|
||||
# The colon may have whitespace on either side. |
|
||||
grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]\+\"" | |
|
||||
# Extract just <val> by deleting the last '"', and then greedily deleting |
|
||||
# everything up to '"'. |
|
||||
sed -e 's/"$//' -e 's/.*"//' |
|
||||
} |
|
||||
|
|
||||
echo ">>> Getting API token..." |
|
||||
jwt=$(curl -sS -X POST \ |
|
||||
-H "Content-Type: application/json" \ |
|
||||
-d "{\"username\":\"${DOCKER_USERNAME}\",\"password\": \"${DOCKER_PASSWORD}\"}" \ |
|
||||
"https://hub.docker.com/v2/users/login" | |
|
||||
extract 'token') |
|
||||
|
|
||||
# Strip the registry portion from `index.docker.io/user/repo`. |
|
||||
repo="${DOCKER_REPO#*/}" |
|
||||
|
|
||||
for arch in ${arches[@]}; do |
for arch in ${arches[@]}; do |
||||
# Don't delete the `arm32v6` tag; Docker can't seem to properly |
platforms+=("${arch_to_platform[$arch]}") |
||||
# auto-select that image on Armv6 platforms like Raspberry Pi 1 and Zero |
|
||||
# (https://github.com/moby/moby/issues/41017). |
|
||||
if [[ ${arch} == 'arm32v6' ]]; then |
|
||||
continue |
|
||||
fi |
|
||||
tag="${DOCKER_TAG}-${arch}" |
|
||||
echo ">>> Deleting '${repo}:${tag}'..." |
|
||||
curl -sS -X DELETE \ |
|
||||
-H "Authorization: Bearer ${jwt}" \ |
|
||||
"https://hub.docker.com/v2/repositories/${repo}/tags/${tag}/" |
|
||||
done |
done |
||||
|
platforms="$(join "," "${platforms[@]}")" |
||||
|
|
||||
|
# Run the build, pushing the resulting images and multi-arch manifest list to |
||||
|
# Docker Hub. The Dockerfile is read from stdin to avoid sending any build |
||||
|
# context, which isn't needed here since the actual cross-compiled images |
||||
|
# have already been built. |
||||
|
docker buildx build \ |
||||
|
--network host \ |
||||
|
--build-arg LOCAL_REPO="${LOCAL_REPO}" \ |
||||
|
--build-arg DOCKER_TAG="${DOCKER_TAG}" \ |
||||
|
--platform "${platforms}" \ |
||||
|
"${tag_args[@]}" \ |
||||
|
--push \ |
||||
|
- < ./docker/Dockerfile.buildx |
||||
|
|
||||
|
# Add an extra arch-specific tag for `arm32v6`; Docker can't seem to properly |
||||
|
# auto-select that image on ARMv6 platforms like Raspberry Pi 1 and Zero |
||||
|
# (https://github.com/moby/moby/issues/41017). |
||||
|
# |
||||
|
# Note that we use `arm32v6` instead of `armv6` to be consistent with the |
||||
|
# existing bitwarden_rs tags, which adhere to the naming conventions of the |
||||
|
# Docker per-architecture repos (e.g., https://hub.docker.com/u/arm32v6). |
||||
|
# Unfortunately, these per-arch repo names aren't always consistent with the |
||||
|
# corresponding platform (OS/arch/variant) IDs, particularly in the case of |
||||
|
# 32-bit ARM arches (e.g., `linux/arm/v6` is used, not `linux/arm32/v6`). |
||||
|
# |
||||
|
# TODO: It looks like this issue should be fixed starting in Docker 20.10.0, |
||||
|
# so this step can be removed once fixed versions are in wider distribution. |
||||
|
# |
||||
|
# Tags: |
||||
|
# |
||||
|
# testing => testing-arm32v6 |
||||
|
# testing-alpine => <ignored> |
||||
|
# x.y.z => x.y.z-arm32v6, latest-arm32v6 |
||||
|
# x.y.z-alpine => <ignored> |
||||
|
# |
||||
|
if [[ "${DOCKER_TAG}" != *alpine ]]; then |
||||
|
image="${DOCKER_REPO}":"${DOCKER_TAG}" |
||||
|
|
||||
|
# Fetch the multi-arch manifest list and find the digest of the armv6 image. |
||||
|
filter='.manifests|.[]|select(.platform.architecture=="arm" and .platform.variant=="v6")|.digest' |
||||
|
digest="$(docker manifest inspect "${image}" | jq -r "${filter}")" |
||||
|
|
||||
|
# Pull the armv6 image by digest, retag it, and repush it. |
||||
|
docker pull "${DOCKER_REPO}"@"${digest}" |
||||
|
docker tag "${DOCKER_REPO}"@"${digest}" "${image}"-arm32v6 |
||||
|
docker push "${image}"-arm32v6 |
||||
|
|
||||
|
if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then |
||||
|
docker tag "${image}"-arm32v6 "${DOCKER_REPO}:latest"-arm32v6 |
||||
|
docker push "${DOCKER_REPO}:latest"-arm32v6 |
||||
|
fi |
||||
|
fi |
||||
|
Loading…
Reference in new issue