From ca26ed2f44e62da021f983e2767b449816ed0088 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 22 Apr 2026 11:22:46 +0100 Subject: [PATCH] Pin Dockerfile base images by digest --- Dockerfile | 8 ++++---- .../internal/subsystems/deployment-installability.md | 7 ++++++- scripts/installtests/build_release_assets_test.go | 10 ++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8caa1f6d7f..6c6ffa788d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ARG BUILD_AGENT=1 # Build stage for frontend (must be built first for embedding) # Force amd64 platform to avoid slow QEMU emulation during multi-arch builds -FROM --platform=linux/amd64 node:20-alpine AS frontend-builder +FROM --platform=linux/amd64 node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS frontend-builder WORKDIR /app/frontend-modern @@ -25,7 +25,7 @@ RUN --mount=type=cache,id=pulse-npm-cache,target=/root/.npm \ # Build stage for Go backend # Force amd64 platform - Go cross-compiles for all targets anyway, # and this avoids slow QEMU emulation during multi-arch builds -FROM --platform=linux/amd64 golang:1.25.9-alpine AS backend-builder +FROM --platform=linux/amd64 golang:1.25.9-alpine@sha256:5caaf1cca9dc351e13deafbc3879fd4754801acba8653fa9540cea125d01a71f AS backend-builder ARG BUILD_AGENT ARG VERSION @@ -168,7 +168,7 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ # Runtime image for the Docker agent (offered via --target agent_runtime) -FROM alpine:3.20 AS agent_runtime +FROM alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc AS agent_runtime # Use TARGETARCH to select the correct binary for the build platform ARG TARGETARCH @@ -200,7 +200,7 @@ ENV PULSE_NO_AUTO_UPDATE=true ENTRYPOINT ["/usr/local/bin/pulse-agent", "--enable-docker", "--enable-host=false"] # Final stage (Pulse server runtime) -FROM alpine:3.20 AS runtime +FROM alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc AS runtime # Use TARGETARCH to select the correct binary for the build platform ARG TARGETARCH diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 75d3e277ff..6e8ea43db9 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -210,7 +210,8 @@ server-side update execution surfaces. pinned. The canonical workflow surface under `.github/workflows/` must use immutable action SHAs, GitHub-hosted jobs must target an explicit Ubuntu LTS runner image instead of `ubuntu-latest`, and checked-in CI/test Dockerfiles - under this subsystem must not depend on floating `:latest` base tags. + under this subsystem must pin base images by immutable `@sha256` digest and + must not depend on floating `:latest` base tags. Whenever that policy changes, update the owning workflow/install proof files in `scripts/installtests/build_release_assets_test.go` and `scripts/release_control/release_promotion_policy_*` in the same slice. @@ -274,6 +275,10 @@ for the published server and agent images, attest the generated release packet assets from the `release/` directory, and pass the embedded license public key through BuildKit secret mounts instead of Docker build arguments so release metadata and image history cannot re-expose it. +That same supply-chain boundary also owns the checked-in build roots +themselves. `Dockerfile` must pin its Node, Go, and Alpine bases by immutable +manifest-list digest so multi-arch release builds do not silently drift onto a +different upstream filesystem just because a mutable tag was republished. That same dev-runtime dependency-manifest boundary now also owns the maintained Docker engine module floor. `go.mod`, `go.sum`, and `internal/cloudcp/docker/manager.go` must route hosted runtime orchestration diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 3de3a485ea..c851718f0a 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -102,6 +102,10 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) { } dockerfile := string(dockerfileBytes) dockerRequired := []string{ + `FROM --platform=linux/amd64 node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS frontend-builder`, + `FROM --platform=linux/amd64 golang:1.25.9-alpine@sha256:5caaf1cca9dc351e13deafbc3879fd4754801acba8653fa9540cea125d01a71f AS backend-builder`, + `FROM alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc AS agent_runtime`, + `FROM alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc AS runtime`, `COPY scripts/release_ldflags.sh ./scripts/release_ldflags.sh`, `COPY scripts/release_update_key.go ./scripts/release_update_key.go`, `COPY scripts/render_installers.go ./scripts/render_installers.go`, @@ -121,6 +125,12 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) { t.Fatalf("Dockerfile missing canonical release ldflags usage: %s", needle) } } + if strings.Contains(dockerfile, `FROM --platform=linux/amd64 node:20-alpine AS frontend-builder`) || + strings.Contains(dockerfile, `FROM --platform=linux/amd64 golang:1.25.9-alpine AS backend-builder`) || + strings.Contains(dockerfile, `FROM alpine:3.20 AS agent_runtime`) || + strings.Contains(dockerfile, `FROM alpine:3.20 AS runtime`) { + t.Fatal("Dockerfile base images must be pinned by immutable @sha256 digests") + } workflowBytes, err := os.ReadFile(repoFile(".github", "workflows", "deploy-demo-server.yml")) if err != nil {