fix(ci): bound Docker build cache with BuildKit cache mounts

- Move the pnpm store, apk and .next caches into --mount=type=cache so
  dependencies are shared across builds instead of duplicated in fresh
  image layers (was the source of unbounded disk growth).
- Replace the deprecated --keep-storage prune flag in ci-deploy.sh with
  the working --max-used-space=4g (buildx v0.37 renamed the flag). The
  deprecated flag silently did nothing, so the BuildKit cache kept
  growing unbounded (was 15.86GB); it is now capped at 4GB after every
  deploy.
This commit is contained in:
openhands committed 2026-09-08 15:39:13 +02:00
1 parent c4496e710b
commit fabd160250
3 files changed
+23 -8

No files matched your search

+21 -6
View File
@@ -4,22 +4,37 @@ FROM node:26.8.1-alpine AS migrations
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
# Keep the bootstrap aligned with package.json packageManager.
RUN apk add --no-cache git \
# The apk cache is persisted in a BuildKit cache mount so git is not
# re-downloaded on every build.
RUN --mount=type=cache,target=/var/cache/apk \
apk add --no-cache git \
&& npm install -g [email protected]
# The pnpm store is kept in a BuildKit cache mount that persists across builds
# on the builder. This is what stops disk usage from growing unbounded: the
# downloaded dependency store is shared and reused instead of being copied into
# a fresh image layer on every build. Unlike an image layer it is also prunable
# independently, so a hard cap (see ci-deploy.sh) keeps it bounded.
ENV PNPM_HOME=/pnpm PNPM_STORE=/pnpm/store
# pnpm-workspace.yaml + .npmrc must be present too: the lockfile records the
# overrides from pnpm-workspace.yaml, and --frozen-lockfile rejects a build
# where the workspace config is absent (ERR_PNPM_LOCKFILE_CONFIG_MISMATCH).
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* .npmrc* ./
# pnpm fetch: download all deps into $PNPM_STORE first, so only the lockfile
# change (not source changes) invalidates the network-heavy download layer.
RUN pnpm fetch --ignore-scripts
RUN pnpm install --frozen-lockfile --ignore-scripts --offline
# pnpm fetch: download all deps into the shared cache-mounted store.
RUN --mount=type=cache,target=/pnpm \
pnpm fetch --ignore-scripts
# Install offline from the cache-mounted store; the store itself stays in the
# build cache between builds.
RUN --mount=type=cache,target=/pnpm \
pnpm install --frozen-lockfile --ignore-scripts --offline
COPY . .
FROM migrations AS builder
ARG NEXT_DEPLOYMENT_ID="unknown"
ENV NEXT_DEPLOYMENT_ID="$NEXT_DEPLOYMENT_ID"
# Fixture values exist only for this build command; production secrets are runtime-only.
RUN DATABASE_URL="mysql://build:[email protected]:9/build" \
# Cache Next.js build output and webpack caches so rebuilds only redo the
# changed parts.
RUN --mount=type=cache,target=/app/.next/cache \
DATABASE_URL="mysql://build:[email protected]:9/build" \
HOTEL_NAME="Build fixture" APP_URL="http://localhost:3002" \
AUTH_SECRET="build-fixture-not-for-runtime-use-000000000000" \
pnpm run build
+1 -1
View File
@@ -176,5 +176,5 @@ while IFS= read -r tag; do
if [ -n "$tagged_image" ] && [ "$tagged_image" != "$previous_image" ]; then docker image rm "$tag" || true; fi
fi
done < <(docker image ls --format '{{.Repository}}:{{.Tag}}' epicnext-cms)
docker builder prune -af --filter "until=72h" --keep-storage=2g || true
docker builder prune -af --filter "until=72h" --max-used-space=4g || true
docker image prune -f --filter "until=168h" || true
+1 -1
View File
@@ -27,7 +27,7 @@ it("preserves production runtime configuration and recent cache", () => {
expect(deploy).toContain("/app/storage");
expect(deploy).not.toContain("--env-file");
expect(deploy).toContain(
'docker builder prune -af --filter "until=72h" --keep-storage=2g',
'docker builder prune -af --filter "until=72h" --max-used-space=4g',
);
expect(deploy).not.toContain("docker volume prune");
});