mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(ci): pin multi-arch docker base digests
This commit is contained in:
20
Dockerfile
20
Dockerfile
@@ -12,13 +12,17 @@
|
||||
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
|
||||
ARG OPENCLAW_EXTENSIONS=""
|
||||
ARG OPENCLAW_VARIANT=default
|
||||
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:22-bookworm@sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:22-bookworm-slim@sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9"
|
||||
|
||||
# Base images are pinned to SHA256 digests for reproducible builds.
|
||||
# Trade-off: digests must be updated manually when upstream tags move.
|
||||
# To update, run: docker manifest inspect node:22-bookworm (or podman)
|
||||
# and replace the digest below with the current amd64 entry.
|
||||
# and replace the digest below with the current multi-arch manifest list entry.
|
||||
|
||||
FROM node:22-bookworm@sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a AS ext-deps
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps
|
||||
ARG OPENCLAW_EXTENSIONS
|
||||
COPY extensions /tmp/extensions
|
||||
# Copy package.json for opted-in extensions so pnpm resolves their deps.
|
||||
@@ -31,7 +35,7 @@ RUN mkdir -p /out && \
|
||||
done
|
||||
|
||||
# ── Stage 2: Build ──────────────────────────────────────────────
|
||||
FROM node:22-bookworm@sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a AS build
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build
|
||||
|
||||
# Install Bun (required for build scripts)
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
@@ -69,13 +73,15 @@ ENV OPENCLAW_PREFER_PNPM=1
|
||||
RUN pnpm ui:build
|
||||
|
||||
# ── Runtime base images ─────────────────────────────────────────
|
||||
FROM node:22-bookworm@sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a AS base-default
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default
|
||||
ARG OPENCLAW_NODE_BOOKWORM_DIGEST
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm" \
|
||||
org.opencontainers.image.base.digest="sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a"
|
||||
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_DIGEST}"
|
||||
|
||||
FROM node:22-bookworm-slim@sha256:b41c15b715b5d6e3f305e9c6480a2396dd5f130b63add98d3d45760376f20823 AS base-slim
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm-slim" \
|
||||
org.opencontainers.image.base.digest="sha256:b41c15b715b5d6e3f305e9c6480a2396dd5f130b63add98d3d45760376f20823"
|
||||
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST}"
|
||||
|
||||
# ── Stage 3: Runtime ────────────────────────────────────────────
|
||||
FROM base-${OPENCLAW_VARIANT}
|
||||
|
||||
@@ -167,10 +167,11 @@ The main Docker image currently uses:
|
||||
|
||||
- `node:22-bookworm`
|
||||
|
||||
The docker image now publishes OCI base-image annotations (sha256 is an example):
|
||||
The docker image now publishes OCI base-image annotations (sha256 is an example,
|
||||
and points at the pinned multi-arch manifest list for that tag):
|
||||
|
||||
- `org.opencontainers.image.base.name=docker.io/library/node:22-bookworm`
|
||||
- `org.opencontainers.image.base.digest=sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a`
|
||||
- `org.opencontainers.image.base.digest=sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9`
|
||||
- `org.opencontainers.image.source=https://github.com/openclaw/openclaw`
|
||||
- `org.opencontainers.image.url=https://openclaw.ai`
|
||||
- `org.opencontainers.image.documentation=https://docs.openclaw.ai/install/docker`
|
||||
|
||||
@@ -33,16 +33,53 @@ type DependabotConfig = {
|
||||
updates?: DependabotUpdate[];
|
||||
};
|
||||
|
||||
function resolveFirstFromReference(dockerfile: string): string | undefined {
|
||||
const argDefaults = new Map<string, string>();
|
||||
|
||||
for (const line of dockerfile.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
if (trimmed.startsWith("FROM ")) {
|
||||
break;
|
||||
}
|
||||
const argMatch = trimmed.match(/^ARG\s+([A-Z0-9_]+)=(.+)$/);
|
||||
if (!argMatch) {
|
||||
continue;
|
||||
}
|
||||
const [, name, rawValue] = argMatch;
|
||||
const value = rawValue.replace(/^["']|["']$/g, "");
|
||||
argDefaults.set(name, value);
|
||||
}
|
||||
|
||||
const fromLine = dockerfile.split(/\r?\n/).find((line) => line.trimStart().startsWith("FROM "));
|
||||
if (!fromLine) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fromMatch = fromLine.trim().match(/^FROM\s+(\S+?)(?:\s+AS\s+\S+)?$/);
|
||||
if (!fromMatch) {
|
||||
return undefined;
|
||||
}
|
||||
const imageRef = fromMatch[1];
|
||||
const argName =
|
||||
imageRef.match(/^\$\{([A-Z0-9_]+)\}$/)?.[1] ?? imageRef.match(/^\$([A-Z0-9_]+)$/)?.[1];
|
||||
|
||||
if (!argName) {
|
||||
return imageRef;
|
||||
}
|
||||
return argDefaults.get(argName);
|
||||
}
|
||||
|
||||
describe("docker base image pinning", () => {
|
||||
it("pins selected Dockerfile FROM lines to immutable sha256 digests", async () => {
|
||||
for (const dockerfilePath of DIGEST_PINNED_DOCKERFILES) {
|
||||
const dockerfile = await readFile(resolve(repoRoot, dockerfilePath), "utf8");
|
||||
const fromLine = dockerfile
|
||||
.split(/\r?\n/)
|
||||
.find((line) => line.trimStart().startsWith("FROM "));
|
||||
expect(fromLine, `${dockerfilePath} should define a FROM line`).toBeDefined();
|
||||
expect(fromLine, `${dockerfilePath} FROM must be digest-pinned`).toMatch(
|
||||
/^FROM\s+\S+@sha256:[a-f0-9]{64}(?:\s+AS\s+\S+)?$/,
|
||||
const imageRef = resolveFirstFromReference(dockerfile);
|
||||
expect(imageRef, `${dockerfilePath} should define a FROM line`).toBeDefined();
|
||||
expect(imageRef, `${dockerfilePath} FROM must be digest-pinned`).toMatch(
|
||||
/^\S+@sha256:[a-f0-9]{64}$/,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,22 @@ const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
||||
const dockerfilePath = join(repoRoot, "Dockerfile");
|
||||
|
||||
describe("Dockerfile", () => {
|
||||
it("uses shared multi-arch base image refs for all root Node stages", async () => {
|
||||
const dockerfile = await readFile(dockerfilePath, "utf8");
|
||||
expect(dockerfile).toContain(
|
||||
'ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:22-bookworm@sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9"',
|
||||
);
|
||||
expect(dockerfile).toContain(
|
||||
'ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:22-bookworm-slim@sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9"',
|
||||
);
|
||||
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps");
|
||||
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build");
|
||||
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default");
|
||||
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim");
|
||||
expect(dockerfile).toContain("current multi-arch manifest list entry");
|
||||
expect(dockerfile).not.toContain("current amd64 entry");
|
||||
});
|
||||
|
||||
it("installs optional browser dependencies after pnpm install", async () => {
|
||||
const dockerfile = await readFile(dockerfilePath, "utf8");
|
||||
const installIndex = dockerfile.indexOf("pnpm install --frozen-lockfile");
|
||||
|
||||
Reference in New Issue
Block a user