Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions .github/workflows/build-minio-mirror.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Build MinIO Mirror

# Publishes ghcr.io/thepalaceproject/palace-ci-minio, the MinIO image used by the test suites of
# circulation, library-registry and virtual-library-card. MinIO withdrew anonymous public access
# to its own image (Docker Hub, then quay.io), so we host a copy built from its official GitHub
# release binaries. See docker/Dockerfile.minio.mirror for the full rationale.
#
# The image is pinned to a single upstream release and its content is fully determined by the
# checksums in that Dockerfile, so there is no scheduled rebuild: running this again produces the
# same image. It runs only when the Dockerfile or this workflow changes, or on demand.
#
# One-time manual step: GHCR packages are created private. After the first successful run, set the
# package visibility to public (Org -> Packages -> palace-ci-minio -> Package settings), matching
# the other Palace images. Without that, every developer and CI job would need a docker login.

on:
push:
branches:
- main
paths:
- .github/workflows/build-minio-mirror.yml
- docker/Dockerfile.minio.mirror
# Build (but do not push) on PRs that touch the mirror, so a broken Dockerfile or workflow is
# caught in review rather than on main, where the failure would leave the image unpublished.
pull_request:
paths:
- .github/workflows/build-minio-mirror.yml
- docker/Dockerfile.minio.mirror
workflow_dispatch:

concurrency:
group: build-minio-mirror-${{ github.ref_name }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
build:
name: Build MinIO Mirror
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

# See comment here: https://github.com/actions/runner-images/issues/1187#issuecomment-686735760
- name: Disable network offload
run: sudo ethtool -K eth0 tx off rx off

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

# The Dockerfile is the single source of truth for which upstream release we mirror, so the
# tag is read back out of it rather than duplicated here where the two could drift apart.
- name: Determine image and tag
id: image
run: |
set -euo pipefail
image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/palace-ci-minio"
tag=$(sed -n 's/^ARG MINIO_RELEASE=\(.*\)$/\1/p' docker/Dockerfile.minio.mirror | head -1)
if [[ -z "$tag" ]]; then
echo "::error::Could not read MINIO_RELEASE from docker/Dockerfile.minio.mirror"
exit 1
fi
echo "Publishing $image:$tag"
echo "image=$image" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"

# The mirror contains no RUN instructions, so both architectures cross-build on this single
# amd64 runner with no QEMU emulation and no per-arch runner matrix.
- name: Build mirror image
uses: docker/build-push-action@v7
with:
context: .
file: ./docker/Dockerfile.minio.mirror
target: minio
platforms: linux/amd64,linux/arm64
# On a pull request this builds both architectures and verifies the pinned checksums,
# then throws the result away. Only main and manual runs publish.
push: ${{ github.event_name != 'pull_request' }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unmerged branches can overwrite image

A manual dispatch from a non-main branch still logs in and pushes. If that branch changes the Dockerfile without changing MINIO_RELEASE, it overwrites the release tag pinned by the consumer in PR #3767. CI can then pull image contents that have not been merged. Restrict publishing to the intended branch, or give branch builds distinct tags.

# Deliberately no `latest` tag: consumers pin this exact release. Following a moving
# upstream tag is part of how we ended up needing this mirror.
tags: ${{ steps.image.outputs.image }}:${{ steps.image.outputs.tag }}

- name: Verify published image
if: github.event_name != 'pull_request'
run: |
set -euo pipefail
ref="${{ steps.image.outputs.image }}:${{ steps.image.outputs.tag }}"
docker buildx imagetools inspect "$ref"
# Confirm both architectures actually made it into the published manifest list.
platforms=$(docker buildx imagetools inspect "$ref" --raw \
| jq -r '.manifests[] | select(.platform.os != "unknown") | "\(.platform.os)/\(.platform.architecture)"')
echo "Published platforms: $platforms"
for platform in linux/amd64 linux/arm64; do
grep -qx "$platform" <<< "$platforms" \
|| { echo "::error::$platform missing from $ref"; exit 1; }
done
# Smoke-test the runner's native architecture.
docker run --rm --entrypoint /usr/bin/minio "$ref" --version
95 changes: 95 additions & 0 deletions docker/Dockerfile.minio.mirror
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# syntax=docker/dockerfile:1.7
#
# Mirror of the upstream MinIO server image, published as
# ghcr.io/thepalaceproject/palace-ci-minio.
#
# Why this exists
# ---------------
# MinIO withdrew anonymous public access to its server image: first from Docker Hub
# (~13 Sept 2026), then from quay.io (~24 Sept 2026). Both registries now answer 401 to
# anonymous manifest requests for every tag, and the binary download host (dl.min.io) answers
# 410. Every Palace repo that ran `FROM <registry>/minio/minio` in its test setup therefore
# fails before its test suite starts. Pinning an older tag or relying on a local Docker cache
# does not help: the whole repository is gated, and tox-docker passes `pull=True` on every
# build, forcing a fresh pull each time.
#
# The one channel MinIO still serves anonymously is GitHub release assets, so this image is
# assembled from the official release binaries rather than pulled and re-tagged. The binaries
# below are byte-for-byte identical to the ones inside the last upstream image CI used
# (quay.io/minio/minio:latest, labelled RELEASE.2025-09-07T16-13-09Z) — verified by comparing
# their sha256 against /usr/bin/minio and /usr/bin/mc extracted from that image.
#
# Scope
# -----
# This is a bare passthrough. It contains no Palace configuration: no credentials, no buckets,
# no entrypoint script. The repos that consume it (circulation, library-registry,
# virtual-library-card) each configure MinIO differently and keep doing so in their own
# Dockerfiles — they change only their `FROM` line. Baking configuration in here would fork
# the image between repos immediately.
#
# This mirror is a bridge, not a destination; see the retirement ticket for replacing MinIO
# outright. Do not add features to it.
#
# Updating
# --------
# The release strings and their checksums are a matched set. If you bump a version you MUST
# also replace the matching `--checksum=` value, or the build will fail (by design).
# Checksums come from the `.sha256sum` asset published alongside each binary.

ARG BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal:9.6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Base image can change

The workflow says rebuilding produces the same image, but ubi-minimal:9.6 is specified by tag rather than digest. If that base tag changes, a manual rebuild can publish different base layers under the same MinIO release tag. This makes the pinned image less predictable for consumers; pin the base by digest if rebuilds must preserve its contents.


# MinIO publishes one binary per platform rather than a multi-arch artifact, and each has its
# own checksum, so the download is split into a per-architecture stage that `fetch` selects
# from using TARGETARCH. This keeps checksum verification native to BuildKit's ADD.
FROM ${BASE_IMAGE} AS fetch-amd64
ARG MINIO_RELEASE=RELEASE.2025-09-07T16-13-09Z
ARG MC_RELEASE=RELEASE.2025-08-13T08-35-41Z
ADD --chmod=755 --checksum=sha256:7c5bd8512c6e966455b1d198209358b2d191c77a83ab377c4073281065fb855f \
https://github.com/minio/minio/releases/download/${MINIO_RELEASE}/minio.linux-amd64.${MINIO_RELEASE} \
/staging/minio
ADD --chmod=755 --checksum=sha256:01f866e9c5f9b87c2b09116fa5d7c06695b106242d829a8bb32990c00312e891 \
https://github.com/minio/mc/releases/download/${MC_RELEASE}/mc.linux-amd64.${MC_RELEASE} \
/staging/mc

FROM ${BASE_IMAGE} AS fetch-arm64
ARG MINIO_RELEASE=RELEASE.2025-09-07T16-13-09Z
ARG MC_RELEASE=RELEASE.2025-08-13T08-35-41Z
ADD --chmod=755 --checksum=sha256:5c83cd2cf151717ba0243f73e1c7802ff36e272b67144bdd7f1f7d684fd6f03d \
https://github.com/minio/minio/releases/download/${MINIO_RELEASE}/minio.linux-arm64.${MINIO_RELEASE} \
/staging/minio
ADD --chmod=755 --checksum=sha256:14c8c9616cfce4636add161304353244e8de383b2e2752c0e9dad01d4c27c12c \
https://github.com/minio/mc/releases/download/${MC_RELEASE}/mc.linux-arm64.${MC_RELEASE} \
/staging/mc

FROM fetch-${TARGETARCH} AS fetch

FROM ${BASE_IMAGE} AS minio
ARG MINIO_RELEASE=RELEASE.2025-09-07T16-13-09Z
ARG MC_RELEASE=RELEASE.2025-08-13T08-35-41Z

LABEL org.opencontainers.image.title="palace-ci-minio" \
org.opencontainers.image.description="Unmodified MinIO server build, mirrored for Palace CI because upstream withdrew anonymous registry access." \
org.opencontainers.image.version="${MINIO_RELEASE}" \
org.opencontainers.image.source="https://github.com/ThePalaceProject/circulation" \
org.opencontainers.image.vendor="The Palace Project" \
io.palace.minio.release="${MINIO_RELEASE}" \
io.palace.mc.release="${MC_RELEASE}"

COPY --from=fetch /staging/minio /usr/bin/minio
COPY --from=fetch /staging/mc /usr/bin/mc

# Matches the upstream image: MinIO reads credentials from these files when the corresponding
# environment variables are not set, and `mc` needs a writable config dir.
ENV MINIO_ROOT_USER_FILE=access_key \
MINIO_ROOT_PASSWORD_FILE=secret_key \
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
MINIO_CONFIG_ENV_FILE=config.env \
MC_CONFIG_DIR=/tmp/.mc

# Declaring the volume creates the mount point, so no RUN is needed here. Keeping this stage
# free of RUN instructions means the image cross-builds for every architecture without QEMU.
VOLUME ["/data"]
EXPOSE 9000 9001

# A plain, unconfigured server. Consumers override this with their own CMD/ENTRYPOINT.
CMD ["minio", "server", "/data", "--address", ":9000", "--console-address", ":9001"]
Loading