From 5e8b184397f24fa760032285d607c83bb7df666a Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:11:48 -0400 Subject: [PATCH 1/2] fix(image): pin musl in the runtime apk transaction so the image builds reproducibly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `apk add --upgrade` upgrades the dependencies of every package it names. libcrypto3, libssl3, libuuid and su-exec all depend on musl, which was not named, so musl floated with the Alpine v3.24 index. Alpine published musl 1.2.6-r3 around 2026-10-26, the runtime layer picked it up in place of the base image's 1.2.6-r2, and the image digest moved with no Dockerfile change (sha256:e9b799a7… on every PR, against the locked sha256:20295012…). Name musl=1.2.6-r3 so every package the transaction changes carries an exact version, assert it in TestRuntimeDockerfilePinsReproducibleBuildInputs, and record the whole-transaction rule in the runtime-image-relock skill. runtime-image.json moves in the next commit, once CI has published this candidate by digest. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/runtime-image-relock/SKILL.md | 10 ++++++++++ Dockerfile | 4 ++++ workflow_test.go | 1 + 3 files changed, 15 insertions(+) diff --git a/.claude/skills/runtime-image-relock/SKILL.md b/.claude/skills/runtime-image-relock/SKILL.md index 8240d87..4084e67 100644 --- a/.claude/skills/runtime-image-relock/SKILL.md +++ b/.claude/skills/runtime-image-relock/SKILL.md @@ -33,6 +33,16 @@ write a digest CI then rejects. postgres patch release, a new pgvector version, a new alpine branch — update the `tag` string too. It is descriptive (`postgres--pgvector--`) and a wrong one silently mislabels every release that consumes it. + An `apk add` line pins the **whole transaction**, not just the packages + you meant to change: `--upgrade` upgrades their dependencies too, and an + unnamed one floats with the Alpine index (musl moved the digest that way on + 2026-10-26 with no Dockerfile change). List what the line would change + against the pinned base and name every package it prints: + + ```bash + docker run --rm "$(sed -n 's/^ARG POSTGRES_IMAGE=//p' Dockerfile)" sh -c \ + 'apk add --simulate --no-cache --upgrade ' + ``` 2. Push the branch. The `image` job builds the candidate and, on a mismatch, fails "Verify runtime image candidate" printing both digests. 3. Copy the **built** digest into `runtime-image.json` and push again. diff --git a/Dockerfile b/Dockerfile index 5480eec..8303d5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,10 +23,14 @@ RUN mkdir /tmp/pgvector /tmp/pgvector-install && \ FROM ${POSTGRES_IMAGE} AS runtime RUN rm /usr/local/bin/gosu +# --upgrade also upgrades each named package's dependencies, so every package +# this transaction changes is named at its exact version. An unnamed one floats +# with the Alpine index: musl did on 2026-10-26 and moved the image digest. RUN apk add --no-cache --upgrade \ libcrypto3=3.5.9-r0 \ libssl3=3.5.9-r0 \ libuuid=2.42.3-r1 \ + musl=1.2.6-r3 \ su-exec=0.3-r0 && \ ln -s /sbin/su-exec /usr/local/bin/gosu && \ rm /var/log/apk.log diff --git a/workflow_test.go b/workflow_test.go index 2284d41..ecd19e9 100644 --- a/workflow_test.go +++ b/workflow_test.go @@ -245,6 +245,7 @@ func TestRuntimeDockerfilePinsReproducibleBuildInputs(t *testing.T) { require.Contains(t, dockerfile, "libcrypto3=3.5.9-r0") require.Contains(t, dockerfile, "libssl3=3.5.9-r0") require.Contains(t, dockerfile, "libuuid=2.42.3-r1") + require.Contains(t, dockerfile, "musl=1.2.6-r3") require.Contains(t, dockerfile, "su-exec=0.3-r0") require.Contains(t, dockerfile, "RUN rm /usr/local/bin/gosu") require.Contains(t, dockerfile, "ln -s /sbin/su-exec /usr/local/bin/gosu") From 041c6201f2ecd72b1eecb506898d8225507cc0a2 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:25:35 -0400 Subject: [PATCH 2/2] fix(image): relock runtime-image.json to the musl-pinned candidate CI published the candidate for 5e8b184 by digest and built sha256:1d7680adb926a17a378b4f56e4b33ed98b64c8dabf39f2301b6d5d71ff5c75e3, the same digest two local --no-cache builds produce with CI's buildkit and flags. Co-Authored-By: Claude Opus 5.5 --- runtime-image.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-image.json b/runtime-image.json index 7bcf2c5..a371798 100644 --- a/runtime-image.json +++ b/runtime-image.json @@ -1,7 +1,7 @@ { "name": "ghcr.io/codefly-dev/service-postgres", "tag": "postgres-17.10-pgvector-0.8.5-alpine3.24", - "digest": "sha256:202950121004ef53cc303edc8728eda9381d05a7c0dae6faddfd68c139f0b759", + "digest": "sha256:1d7680adb926a17a378b4f56e4b33ed98b64c8dabf39f2301b6d5d71ff5c75e3", "platforms": [ "linux/amd64", "linux/arm64"