mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-11 23:10:29 +00:00
Docker: improve build cache reuse (#40351)
* Docker: improve build cache reuse * Tests: cover Docker build cache layout * Docker: fix sandbox cache mount continuations * Docker: document qr-import manifest scope * Docker: narrow e2e install inputs * CI: cache Docker builds in workflows * CI: route sandbox smoke through setup script * CI: keep sandbox smoke on script path
This commit is contained in:
8
.github/workflows/docker-release.yml
vendored
8
.github/workflows/docker-release.yml
vendored
@@ -109,6 +109,8 @@ jobs:
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
cache-from: type=gha,scope=docker-release-amd64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-amd64
|
||||
|
||||
- name: Build and push amd64 slim image
|
||||
id: build-slim
|
||||
@@ -122,6 +124,8 @@ jobs:
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
cache-from: type=gha,scope=docker-release-amd64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-amd64
|
||||
|
||||
# Build arm64 images (default + slim share the build stage cache)
|
||||
build-arm64:
|
||||
@@ -210,6 +214,8 @@ jobs:
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
cache-from: type=gha,scope=docker-release-arm64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-arm64
|
||||
|
||||
- name: Build and push arm64 slim image
|
||||
id: build-slim
|
||||
@@ -223,6 +229,8 @@ jobs:
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
cache-from: type=gha,scope=docker-release-arm64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-arm64
|
||||
|
||||
# Create multi-platform manifests
|
||||
create-manifest:
|
||||
|
||||
38
Dockerfile
38
Dockerfile
@@ -1,3 +1,5 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
# Opt-in extension dependencies at build time (space-separated directory names).
|
||||
# Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel matrix" .
|
||||
#
|
||||
@@ -48,13 +50,13 @@ WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY patches ./patches
|
||||
COPY scripts ./scripts
|
||||
|
||||
COPY --from=ext-deps /out/ ./extensions/
|
||||
|
||||
# Reduce OOM risk on low-memory hosts during dependency installation.
|
||||
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
||||
RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -117,11 +119,11 @@ WORKDIR /app
|
||||
|
||||
# Install system utilities present in bookworm but missing in bookworm-slim.
|
||||
# On the full bookworm image these are already installed (apt-get is a no-op).
|
||||
RUN apt-get update && \
|
||||
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
procps hostname curl git openssl && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
procps hostname curl git openssl
|
||||
|
||||
RUN chown node:node /app
|
||||
|
||||
@@ -145,11 +147,11 @@ RUN install -d -m 0755 "$COREPACK_HOME" && \
|
||||
# Install additional system packages needed by your skills or extensions.
|
||||
# Example: docker build --build-arg OPENCLAW_DOCKER_APT_PACKAGES="python3 wget" .
|
||||
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
|
||||
RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
||||
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES; \
|
||||
fi
|
||||
|
||||
# Optionally install Chromium and Xvfb for browser automation.
|
||||
@@ -157,15 +159,15 @@ RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
||||
# Adds ~300MB but eliminates the 60-90s Playwright install on every container start.
|
||||
# Must run after node_modules COPY so playwright-core is available.
|
||||
ARG OPENCLAW_INSTALL_BROWSER=""
|
||||
RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
|
||||
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends xvfb && \
|
||||
mkdir -p /home/node/.cache/ms-playwright && \
|
||||
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright \
|
||||
node /app/node_modules/playwright-core/cli.js install --with-deps chromium && \
|
||||
chown -R node:node /home/node/.cache/ms-playwright && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
chown -R node:node /home/node/.cache/ms-playwright; \
|
||||
fi
|
||||
|
||||
# Optionally install Docker CLI for sandbox container management.
|
||||
@@ -174,7 +176,9 @@ RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
|
||||
# Required for agents.defaults.sandbox to function in Docker deployments.
|
||||
ARG OPENCLAW_INSTALL_DOCKER_CLI=""
|
||||
ARG OPENCLAW_DOCKER_GPG_FINGERPRINT="9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
|
||||
RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
|
||||
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl gnupg && \
|
||||
@@ -195,9 +199,7 @@ RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
|
||||
"$(dpkg --print-architecture)" > /etc/apt/sources.list.d/docker.list && \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
docker-ce-cli docker-compose-plugin && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
docker-ce-cli docker-compose-plugin; \
|
||||
fi
|
||||
|
||||
# Expose the CLI binary without requiring npm global writes as non-root.
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
@@ -10,8 +14,7 @@ RUN apt-get update \
|
||||
git \
|
||||
jq \
|
||||
python3 \
|
||||
ripgrep \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
ripgrep
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash sandbox
|
||||
USER sandbox
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
@@ -17,8 +21,7 @@ RUN apt-get update \
|
||||
socat \
|
||||
websockify \
|
||||
x11vnc \
|
||||
xvfb \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
xvfb
|
||||
|
||||
COPY --chmod=755 scripts/sandbox-browser-entrypoint.sh /usr/local/bin/openclaw-sandbox-browser
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
ARG BASE_IMAGE=openclaw-sandbox:bookworm-slim
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
@@ -19,9 +21,10 @@ ENV HOMEBREW_CELLAR=${BREW_INSTALL_DIR}/Cellar
|
||||
ENV HOMEBREW_REPOSITORY=${BREW_INSTALL_DIR}/Homebrew
|
||||
ENV PATH=${BUN_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/sbin:${PATH}
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ${PACKAGES} \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-common-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-common-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ${PACKAGES}
|
||||
|
||||
RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi
|
||||
|
||||
@@ -42,4 +45,3 @@ fi
|
||||
|
||||
# Default is sandbox, but allow BASE_IMAGE overrides to select another final user.
|
||||
USER ${FINAL_USER}
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
|
||||
|
||||
RUN apt-get update \
|
||||
RUN --mount=type=cache,id=openclaw-cleanup-smoke-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-cleanup-smoke-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
git
|
||||
|
||||
WORKDIR /repo
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
RUN corepack enable \
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
corepack enable \
|
||||
&& pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
|
||||
|
||||
RUN apt-get update \
|
||||
RUN --mount=type=cache,id=openclaw-install-sh-e2e-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-install-sh-e2e-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
git
|
||||
|
||||
COPY install-sh-common/version-parse.sh /usr/local/install-sh-common/version-parse.sh
|
||||
COPY --chmod=755 run.sh /usr/local/bin/openclaw-install-e2e
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b
|
||||
|
||||
RUN set -eux; \
|
||||
RUN --mount=type=cache,id=openclaw-install-sh-nonroot-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-install-sh-nonroot-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
set -eux; \
|
||||
for attempt in 1 2 3; do \
|
||||
if apt-get update -o Acquire::Retries=3; then break; fi; \
|
||||
echo "apt-get update failed (attempt ${attempt})" >&2; \
|
||||
@@ -14,8 +18,7 @@ RUN set -eux; \
|
||||
g++ \
|
||||
make \
|
||||
python3 \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
sudo
|
||||
|
||||
RUN useradd -m -s /bin/bash app \
|
||||
&& echo "app ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/app
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
|
||||
|
||||
RUN set -eux; \
|
||||
RUN --mount=type=cache,id=openclaw-install-sh-smoke-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-install-sh-smoke-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
set -eux; \
|
||||
for attempt in 1 2 3; do \
|
||||
if apt-get update -o Acquire::Retries=3; then break; fi; \
|
||||
echo "apt-get update failed (attempt ${attempt})" >&2; \
|
||||
@@ -15,8 +19,7 @@ RUN set -eux; \
|
||||
g++ \
|
||||
make \
|
||||
python3 \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
sudo
|
||||
|
||||
COPY install-sh-common/cli-verify.sh /usr/local/install-sh-common/cli-verify.sh
|
||||
COPY install-sh-common/version-parse.sh /usr/local/install-sh-common/version-parse.sh
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
|
||||
|
||||
RUN corepack enable
|
||||
@@ -6,20 +8,26 @@ WORKDIR /app
|
||||
|
||||
ENV NODE_OPTIONS="--disable-warning=ExperimentalWarning"
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json tsconfig.plugin-sdk.dts.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY extensions/memory-core/package.json ./extensions/memory-core/package.json
|
||||
COPY patches ./patches
|
||||
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
COPY tsconfig.json tsconfig.plugin-sdk.dts.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./
|
||||
COPY src ./src
|
||||
COPY test ./test
|
||||
COPY scripts ./scripts
|
||||
COPY docs ./docs
|
||||
COPY skills ./skills
|
||||
COPY patches ./patches
|
||||
COPY ui ./ui
|
||||
COPY extensions/memory-core ./extensions/memory-core
|
||||
COPY vendor/a2ui/renderers/lit ./vendor/a2ui/renderers/lit
|
||||
COPY apps/shared/OpenClawKit/Sources/OpenClawKit/Resources ./apps/shared/OpenClawKit/Sources/OpenClawKit/Resources
|
||||
COPY apps/shared/OpenClawKit/Tools/CanvasA2UI ./apps/shared/OpenClawKit/Tools/CanvasA2UI
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm build
|
||||
RUN pnpm ui:build
|
||||
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY patches ./patches
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
# This image only exercises the root qrcode-terminal dependency path.
|
||||
# Keep the pre-install copy set limited to the manifests needed for root
|
||||
# workspace resolution so unrelated extension edits do not bust the layer.
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash appuser \
|
||||
&& chown -R appuser:appuser /app
|
||||
|
||||
@@ -10,6 +10,9 @@ BUN_INSTALL_DIR="${BUN_INSTALL_DIR:-/opt/bun}"
|
||||
INSTALL_BREW="${INSTALL_BREW:-1}"
|
||||
BREW_INSTALL_DIR="${BREW_INSTALL_DIR:-/home/linuxbrew/.linuxbrew}"
|
||||
FINAL_USER="${FINAL_USER:-sandbox}"
|
||||
OPENCLAW_DOCKER_BUILD_USE_BUILDX="${OPENCLAW_DOCKER_BUILD_USE_BUILDX:-0}"
|
||||
OPENCLAW_DOCKER_BUILD_CACHE_FROM="${OPENCLAW_DOCKER_BUILD_CACHE_FROM:-}"
|
||||
OPENCLAW_DOCKER_BUILD_CACHE_TO="${OPENCLAW_DOCKER_BUILD_CACHE_TO:-}"
|
||||
|
||||
if ! docker image inspect "${BASE_IMAGE}" >/dev/null 2>&1; then
|
||||
echo "Base image missing: ${BASE_IMAGE}"
|
||||
@@ -19,7 +22,18 @@ fi
|
||||
|
||||
echo "Building ${TARGET_IMAGE} with: ${PACKAGES}"
|
||||
|
||||
docker build \
|
||||
build_cmd=(docker build)
|
||||
if [ "${OPENCLAW_DOCKER_BUILD_USE_BUILDX}" = "1" ]; then
|
||||
build_cmd=(docker buildx build --load)
|
||||
if [ -n "${OPENCLAW_DOCKER_BUILD_CACHE_FROM}" ]; then
|
||||
build_cmd+=(--cache-from "${OPENCLAW_DOCKER_BUILD_CACHE_FROM}")
|
||||
fi
|
||||
if [ -n "${OPENCLAW_DOCKER_BUILD_CACHE_TO}" ]; then
|
||||
build_cmd+=(--cache-to "${OPENCLAW_DOCKER_BUILD_CACHE_TO}")
|
||||
fi
|
||||
fi
|
||||
|
||||
"${build_cmd[@]}" \
|
||||
-t "${TARGET_IMAGE}" \
|
||||
-f Dockerfile.sandbox-common \
|
||||
--build-arg BASE_IMAGE="${BASE_IMAGE}" \
|
||||
|
||||
127
src/docker-build-cache.test.ts
Normal file
127
src/docker-build-cache.test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
||||
|
||||
async function readRepoFile(path: string): Promise<string> {
|
||||
return readFile(resolve(repoRoot, path), "utf8");
|
||||
}
|
||||
|
||||
describe("docker build cache layout", () => {
|
||||
it("keeps the root dependency layer independent from scripts changes", async () => {
|
||||
const dockerfile = await readRepoFile("Dockerfile");
|
||||
const installIndex = dockerfile.indexOf("pnpm install --frozen-lockfile");
|
||||
const copyAllIndex = dockerfile.indexOf("COPY . .");
|
||||
const scriptsCopyIndex = dockerfile.indexOf("COPY scripts ./scripts");
|
||||
|
||||
expect(installIndex).toBeGreaterThan(-1);
|
||||
expect(copyAllIndex).toBeGreaterThan(installIndex);
|
||||
expect(scriptsCopyIndex === -1 || scriptsCopyIndex > installIndex).toBe(true);
|
||||
});
|
||||
|
||||
it("uses pnpm cache mounts in Dockerfiles that install repo dependencies", async () => {
|
||||
for (const path of [
|
||||
"Dockerfile",
|
||||
"scripts/e2e/Dockerfile",
|
||||
"scripts/e2e/Dockerfile.qr-import",
|
||||
"scripts/docker/cleanup-smoke/Dockerfile",
|
||||
]) {
|
||||
const dockerfile = await readRepoFile(path);
|
||||
expect(dockerfile, `${path} should use a shared pnpm store cache`).toContain(
|
||||
"--mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses apt cache mounts in Dockerfiles that install system packages", async () => {
|
||||
for (const path of [
|
||||
"Dockerfile",
|
||||
"Dockerfile.sandbox",
|
||||
"Dockerfile.sandbox-browser",
|
||||
"Dockerfile.sandbox-common",
|
||||
"scripts/docker/cleanup-smoke/Dockerfile",
|
||||
"scripts/docker/install-sh-smoke/Dockerfile",
|
||||
"scripts/docker/install-sh-e2e/Dockerfile",
|
||||
"scripts/docker/install-sh-nonroot/Dockerfile",
|
||||
]) {
|
||||
const dockerfile = await readRepoFile(path);
|
||||
expect(dockerfile, `${path} should cache apt package archives`).toContain(
|
||||
"target=/var/cache/apt,sharing=locked",
|
||||
);
|
||||
expect(dockerfile, `${path} should cache apt metadata`).toContain(
|
||||
"target=/var/lib/apt,sharing=locked",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not leave empty shell continuation lines in sandbox-common", async () => {
|
||||
const dockerfile = await readRepoFile("Dockerfile.sandbox-common");
|
||||
expect(dockerfile).not.toContain("apt-get install -y --no-install-recommends ${PACKAGES} \\");
|
||||
expect(dockerfile).toContain(
|
||||
'RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi',
|
||||
);
|
||||
});
|
||||
|
||||
it("does not leave blank lines after shell continuation markers", async () => {
|
||||
for (const path of [
|
||||
"Dockerfile.sandbox",
|
||||
"Dockerfile.sandbox-browser",
|
||||
"Dockerfile.sandbox-common",
|
||||
"scripts/docker/cleanup-smoke/Dockerfile",
|
||||
"scripts/docker/install-sh-smoke/Dockerfile",
|
||||
"scripts/docker/install-sh-e2e/Dockerfile",
|
||||
"scripts/docker/install-sh-nonroot/Dockerfile",
|
||||
]) {
|
||||
const dockerfile = await readRepoFile(path);
|
||||
expect(
|
||||
dockerfile,
|
||||
`${path} should not have blank lines after a trailing backslash`,
|
||||
).not.toMatch(/\\\n\s*\n/);
|
||||
}
|
||||
});
|
||||
|
||||
it("copies only install inputs before pnpm install in the e2e image", async () => {
|
||||
const dockerfile = await readRepoFile("scripts/e2e/Dockerfile");
|
||||
const installIndex = dockerfile.indexOf("pnpm install --frozen-lockfile");
|
||||
|
||||
expect(
|
||||
dockerfile.indexOf("COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./"),
|
||||
).toBeLessThan(installIndex);
|
||||
expect(dockerfile.indexOf("COPY ui/package.json ./ui/package.json")).toBeLessThan(installIndex);
|
||||
expect(
|
||||
dockerfile.indexOf(
|
||||
"COPY extensions/memory-core/package.json ./extensions/memory-core/package.json",
|
||||
),
|
||||
).toBeLessThan(installIndex);
|
||||
expect(
|
||||
dockerfile.indexOf(
|
||||
"COPY tsconfig.json tsconfig.plugin-sdk.dts.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./",
|
||||
),
|
||||
).toBeGreaterThan(installIndex);
|
||||
expect(dockerfile.indexOf("COPY src ./src")).toBeGreaterThan(installIndex);
|
||||
expect(dockerfile.indexOf("COPY test ./test")).toBeGreaterThan(installIndex);
|
||||
expect(dockerfile.indexOf("COPY scripts ./scripts")).toBeGreaterThan(installIndex);
|
||||
expect(dockerfile.indexOf("COPY ui ./ui")).toBeGreaterThan(installIndex);
|
||||
});
|
||||
|
||||
it("copies manifests before install in the qr-import image", async () => {
|
||||
const dockerfile = await readRepoFile("scripts/e2e/Dockerfile.qr-import");
|
||||
const installIndex = dockerfile.indexOf("pnpm install --frozen-lockfile");
|
||||
|
||||
expect(
|
||||
dockerfile.indexOf("COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./"),
|
||||
).toBeLessThan(installIndex);
|
||||
expect(dockerfile.indexOf("COPY ui/package.json ./ui/package.json")).toBeLessThan(installIndex);
|
||||
expect(dockerfile).toContain(
|
||||
"This image only exercises the root qrcode-terminal dependency path.",
|
||||
);
|
||||
expect(
|
||||
dockerfile.indexOf(
|
||||
"COPY extensions/memory-core/package.json ./extensions/memory-core/package.json",
|
||||
),
|
||||
).toBe(-1);
|
||||
expect(dockerfile.indexOf("COPY . .")).toBeGreaterThan(installIndex);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user