diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml
index 4ebef9f..2a653f1 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -22,6 +22,8 @@ jobs:
arch: arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
+ env:
+ APT_MIRROR: http://azure.archive.ubuntu.com/ubuntu/
steps:
- name: Checkout repository
diff --git a/.github/workflows/build-oci.yaml b/.github/workflows/build-oci.yaml
index 9ebca8f..96b3343 100644
--- a/.github/workflows/build-oci.yaml
+++ b/.github/workflows/build-oci.yaml
@@ -37,6 +37,8 @@ jobs:
runs-on: ${{ matrix.runner }}
# NOTE: 90 min covers the qemu lanes; native lanes finish well under 30.
timeout-minutes: 90
+ env:
+ APT_MIRROR: http://azure.archive.ubuntu.com/ubuntu/
steps:
- name: Checkout repository
@@ -58,7 +60,7 @@ jobs:
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: images-out/
- key: images-${{ matrix.arch }}-${{ hashFiles('images/Dockerfile.*', 'hack/bread-warning.sh', 'hack/banner.txt', 'hack/lazy-apt.sh', 'hack/build_image.sh', '.stamp/binaries') }}
+ key: images-${{ matrix.arch }}-${{ hashFiles('images/Dockerfile.*', 'hack/bread-warning.sh', 'hack/banner.txt', 'hack/lazy-apt.sh', 'hack/apt-mirror.sh', 'hack/build_image.sh', '.stamp/binaries') }}
- name: Set up qemu (emulated arches only)
if: matrix.qemu && steps.images-cache.outputs.cache-hit != 'true'
diff --git a/README.md b/README.md
index da1355e..6447894 100644
--- a/README.md
+++ b/README.md
@@ -100,6 +100,7 @@ spread-bread/
check_base.sh # detect upstream ubuntu base digest drift; rewrite @sha256 pins
inline_scripts.rb # splice scripts/*.sh into yaml templates
tar-shim.sh # image /bin/tar; routes extraction to bsdtar where gnu tar is broken
+ apt-mirror.sh # build-time apt mirror override, bind-mounted into image builds by ci
scripts/ # allocate / discard scripts, one pair per flavour
images/ # one Dockerfile per (flavour, ubuntu version)
templates/ # yaml templates with `source scripts/...` markers
diff --git a/hack/apt-mirror.sh b/hack/apt-mirror.sh
new file mode 100755
index 0000000..a017804
--- /dev/null
+++ b/hack/apt-mirror.sh
@@ -0,0 +1,30 @@
+#!/bin/sh -e
+# bread-apt-mirror: build-time apt mirror override for docker build, which
+# otherwise pulls from archive.ubuntu.com; on ci that is the slow path, while
+# the runner itself uses the azure mirror. Same mirror, same failover shape:
+# https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/configure-apt-sources.sh
+# https://manpages.ubuntu.com/manpages/noble/en/man1/apt-transport-mirror.1.html
+#
+# Writes into the scratch dir $1 copies of the apt sources with the archive
+# uris pointed at a mirrorlist ($APT_MIRROR first, archive.ubuntu.com second)
+# plus an apt.conf that makes apt read those copies; the build exports
+# APT_CONFIG=
/apt.conf. The image's own /etc/apt is never touched.
+# Without APT_MIRROR the apt.conf is empty and apt behaves as stock.
+#
+# Usage: bread-apt-mirror
+
+dir="${1:?scratch dir required}"
+mkdir -p "$dir/sources.list.d"
+: > "$dir/apt.conf"
+[ -n "${APT_MIRROR:-}" ] || exit 0
+
+list="$dir/mirrors.txt"
+printf '%s\tpriority:1\nhttp://archive.ubuntu.com/ubuntu/\tpriority:2\n' "$APT_MIRROR" > "$list"
+
+: > "$dir/sources.list"
+for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do
+ [ -f "$f" ] || continue
+ sed "s|http://archive\.ubuntu\.com/ubuntu/|mirror+file:$list|g" "$f" > "$dir/${f#/etc/apt/}"
+done
+
+printf 'Dir::Etc::sourcelist "%s/sources.list";\nDir::Etc::sourceparts "%s/sources.list.d";\n' "$dir" "$dir" > "$dir/apt.conf"
diff --git a/hack/build_image.sh b/hack/build_image.sh
index 7b890e5..aea3222 100755
--- a/hack/build_image.sh
+++ b/hack/build_image.sh
@@ -30,6 +30,7 @@ case "$flavour" in
bread)
docker build \
--tag "bread:$ver-$arch" \
+ --build-arg "APT_MIRROR=${APT_MIRROR:-}" \
--file "images/Dockerfile.bread-$ver" \
--platform "linux/$arch" \
.
@@ -38,6 +39,7 @@ case "$flavour" in
docker build \
--tag "bread-chisel-releases:$ver-$arch" \
--build-arg "BASE_TAG=$ver-$arch" \
+ --build-arg "APT_MIRROR=${APT_MIRROR:-}" \
--build-arg "BUILD_ARCH=$arch" \
--file "images/Dockerfile.bread-chisel-releases-$ver" \
--platform "linux/$arch" \
diff --git a/hack/hash_inputs.sh b/hack/hash_inputs.sh
index ad3edd4..262b0c6 100755
--- a/hack/hash_inputs.sh
+++ b/hack/hash_inputs.sh
@@ -40,12 +40,14 @@ case "$flavour" in
"hack/bread-warning.sh"
"hack/banner.txt"
"hack/tar-shim.sh"
+ "hack/apt-mirror.sh"
)
;;
bread-chisel-releases)
inputs=(
"images/Dockerfile.bread-chisel-releases-$ver"
"hack/lazy-apt.sh"
+ "hack/apt-mirror.sh"
".stamp/bread-$ver-$arch"
".stamp/binaries"
)
diff --git a/images/Dockerfile.bread-22.04 b/images/Dockerfile.bread-22.04
index 4c6e109..972b72a 100644
--- a/images/Dockerfile.bread-22.04
+++ b/images/Dockerfile.bread-22.04
@@ -2,14 +2,19 @@
FROM docker.io/library/ubuntu:22.04@sha256:829f6df217bcbae2b371026e81711d1a787c61b2967ad09d015063663ebafbf7
+ARG APT_MIRROR=
+
# Skip man/doc/info install for every subsequent package -- dpkg's man-db
# trigger is a major cost under qemu emulation (s390x / ppc64le). Persists in
# the image fs, so the bread-chisel-releases install inherits the exclusion.
-RUN printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
> /etc/dpkg/dpkg.cfg.d/01-nodoc && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server libarchive-tools && \
- mkdir /var/run/sshd || true && \
+ mkdir -p /var/run/sshd && \
echo 'root:bread' | chpasswd && \
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
diff --git a/images/Dockerfile.bread-24.04 b/images/Dockerfile.bread-24.04
index 46b41d9..7553b4f 100644
--- a/images/Dockerfile.bread-24.04
+++ b/images/Dockerfile.bread-24.04
@@ -2,14 +2,19 @@
FROM docker.io/library/ubuntu:24.04@sha256:224a1869083a311ef3f13648a154ba79832fbef6364d31493642ca03082da254
+ARG APT_MIRROR=
+
# Skip man/doc/info install for every subsequent package -- dpkg's man-db
# trigger is a major cost under qemu emulation (s390x / ppc64le). Persists in
# the image fs, so the bread-chisel-releases install inherits the exclusion.
-RUN printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
> /etc/dpkg/dpkg.cfg.d/01-nodoc && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server libarchive-tools && \
- mkdir /var/run/sshd || true && \
+ mkdir -p /var/run/sshd && \
echo 'root:bread' | chpasswd && \
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
diff --git a/images/Dockerfile.bread-25.10 b/images/Dockerfile.bread-25.10
index 8d5df43..0ec91ed 100644
--- a/images/Dockerfile.bread-25.10
+++ b/images/Dockerfile.bread-25.10
@@ -2,14 +2,19 @@
FROM docker.io/library/ubuntu:25.10@sha256:7cc5e35f6567ee8c66d2abb4aab0fd866669e6207c237c3a8f0947a5c7f17092
+ARG APT_MIRROR=
+
# Skip man/doc/info install for every subsequent package -- dpkg's man-db
# trigger is a major cost under qemu emulation (s390x / ppc64le). Persists in
# the image fs, so the bread-chisel-releases install inherits the exclusion.
-RUN printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
> /etc/dpkg/dpkg.cfg.d/01-nodoc && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server libarchive-tools && \
- mkdir /var/run/sshd || true && \
+ mkdir -p /var/run/sshd && \
echo 'root:bread' | chpasswd && \
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
diff --git a/images/Dockerfile.bread-26.04 b/images/Dockerfile.bread-26.04
index 3a16769..8db13b3 100644
--- a/images/Dockerfile.bread-26.04
+++ b/images/Dockerfile.bread-26.04
@@ -2,14 +2,19 @@
FROM docker.io/library/ubuntu:26.04@sha256:513c074113a871b51a8d16ab445c88779d6452d937a164fb5cc479f32668a41d
+ARG APT_MIRROR=
+
# Skip man/doc/info install for every subsequent package -- dpkg's man-db
# trigger is a major cost under qemu emulation (s390x / ppc64le). Persists in
# the image fs, so the bread-chisel-releases install inherits the exclusion.
-RUN printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
> /etc/dpkg/dpkg.cfg.d/01-nodoc && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server libarchive-tools && \
- mkdir /var/run/sshd || true && \
+ mkdir -p /var/run/sshd && \
echo 'root:bread' | chpasswd && \
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
diff --git a/images/Dockerfile.bread-26.10 b/images/Dockerfile.bread-26.10
index 4a8efaf..b6521fe 100644
--- a/images/Dockerfile.bread-26.10
+++ b/images/Dockerfile.bread-26.10
@@ -2,14 +2,19 @@
FROM docker.io/library/ubuntu:26.10@sha256:49077a16b772f8bc6e6f160ad2bfc218919f3455037387ed19a8309174328603
+ARG APT_MIRROR=
+
# Skip man/doc/info install for every subsequent package -- dpkg's man-db
# trigger is a major cost under qemu emulation (s390x / ppc64le). Persists in
# the image fs, so the bread-chisel-releases install inherits the exclusion.
-RUN printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ printf 'path-exclude /usr/share/man/*\npath-exclude /usr/share/doc/*\npath-exclude /usr/share/info/*\n' \
> /etc/dpkg/dpkg.cfg.d/01-nodoc && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server libarchive-tools && \
- mkdir /var/run/sshd || true && \
+ mkdir -p /var/run/sshd && \
echo 'root:bread' | chpasswd && \
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
diff --git a/images/Dockerfile.bread-chisel-releases-22.04 b/images/Dockerfile.bread-chisel-releases-22.04
index c55d086..0f76c7c 100644
--- a/images/Dockerfile.bread-chisel-releases-22.04
+++ b/images/Dockerfile.bread-chisel-releases-22.04
@@ -9,8 +9,12 @@ ARG BASE_TAG=22.04-amd64
ARG BUILD_ARCH=amd64
FROM bread:${BASE_TAG}
ARG BUILD_ARCH
+ARG APT_MIRROR=
-RUN apt-get update && \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl wget git jq file sudo tree \
skopeo iproute2 && \
diff --git a/images/Dockerfile.bread-chisel-releases-24.04 b/images/Dockerfile.bread-chisel-releases-24.04
index 5a0b848..0db0b3b 100644
--- a/images/Dockerfile.bread-chisel-releases-24.04
+++ b/images/Dockerfile.bread-chisel-releases-24.04
@@ -9,8 +9,12 @@ ARG BASE_TAG=24.04-amd64
ARG BUILD_ARCH=amd64
FROM bread:${BASE_TAG}
ARG BUILD_ARCH
+ARG APT_MIRROR=
-RUN apt-get update && \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl wget git jq file sudo tree \
skopeo iproute2 && \
diff --git a/images/Dockerfile.bread-chisel-releases-25.10 b/images/Dockerfile.bread-chisel-releases-25.10
index cda0585..c85c322 100644
--- a/images/Dockerfile.bread-chisel-releases-25.10
+++ b/images/Dockerfile.bread-chisel-releases-25.10
@@ -9,8 +9,12 @@ ARG BASE_TAG=25.10-amd64
ARG BUILD_ARCH=amd64
FROM bread:${BASE_TAG}
ARG BUILD_ARCH
+ARG APT_MIRROR=
-RUN apt-get update && \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl wget git jq file sudo tree \
skopeo iproute2 && \
diff --git a/images/Dockerfile.bread-chisel-releases-26.04 b/images/Dockerfile.bread-chisel-releases-26.04
index 8fc1544..1c979f6 100644
--- a/images/Dockerfile.bread-chisel-releases-26.04
+++ b/images/Dockerfile.bread-chisel-releases-26.04
@@ -9,8 +9,12 @@ ARG BASE_TAG=26.04-amd64
ARG BUILD_ARCH=amd64
FROM bread:${BASE_TAG}
ARG BUILD_ARCH
+ARG APT_MIRROR=
-RUN apt-get update && \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl wget git jq file sudo tree \
skopeo iproute2 && \
diff --git a/images/Dockerfile.bread-chisel-releases-26.10 b/images/Dockerfile.bread-chisel-releases-26.10
index 541c279..a26f87b 100644
--- a/images/Dockerfile.bread-chisel-releases-26.10
+++ b/images/Dockerfile.bread-chisel-releases-26.10
@@ -9,8 +9,12 @@ ARG BASE_TAG=26.10-amd64
ARG BUILD_ARCH=amd64
FROM bread:${BASE_TAG}
ARG BUILD_ARCH
+ARG APT_MIRROR=
-RUN apt-get update && \
+RUN --mount=type=bind,source=hack/apt-mirror.sh,target=/usr/local/bin/bread-apt-mirror \
+ --mount=type=tmpfs,target=/run/apt-mirror \
+ export APT_CONFIG=/run/apt-mirror/apt.conf && bread-apt-mirror /run/apt-mirror && \
+ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl wget git jq file sudo tree \
skopeo iproute2 && \