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:
1 parent
c4496e710b
commit
fabd160250
3 files changed
+23
-8
No files matched your search
+21
-6
@@ -4,22 +4,37 @@ FROM node:26.8.1-alpine AS migrations
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
# Keep the bootstrap aligned with package.json packageManager.
|
# 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]
|
&& 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
|
# pnpm-workspace.yaml + .npmrc must be present too: the lockfile records the
|
||||||
# overrides from pnpm-workspace.yaml, and --frozen-lockfile rejects a build
|
# overrides from pnpm-workspace.yaml, and --frozen-lockfile rejects a build
|
||||||
# where the workspace config is absent (ERR_PNPM_LOCKFILE_CONFIG_MISMATCH).
|
# where the workspace config is absent (ERR_PNPM_LOCKFILE_CONFIG_MISMATCH).
|
||||||
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* .npmrc* ./
|
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* .npmrc* ./
|
||||||
# pnpm fetch: download all deps into $PNPM_STORE first, so only the lockfile
|
# pnpm fetch: download all deps into the shared cache-mounted store.
|
||||||
# change (not source changes) invalidates the network-heavy download layer.
|
RUN --mount=type=cache,target=/pnpm \
|
||||||
RUN pnpm fetch --ignore-scripts
|
pnpm fetch --ignore-scripts
|
||||||
RUN pnpm install --frozen-lockfile --ignore-scripts --offline
|
# 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 . .
|
COPY . .
|
||||||
FROM migrations AS builder
|
FROM migrations AS builder
|
||||||
ARG NEXT_DEPLOYMENT_ID="unknown"
|
ARG NEXT_DEPLOYMENT_ID="unknown"
|
||||||
ENV NEXT_DEPLOYMENT_ID="$NEXT_DEPLOYMENT_ID"
|
ENV NEXT_DEPLOYMENT_ID="$NEXT_DEPLOYMENT_ID"
|
||||||
# Fixture values exist only for this build command; production secrets are runtime-only.
|
# 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" \
|
HOTEL_NAME="Build fixture" APP_URL="http://localhost:3002" \
|
||||||
AUTH_SECRET="build-fixture-not-for-runtime-use-000000000000" \
|
AUTH_SECRET="build-fixture-not-for-runtime-use-000000000000" \
|
||||||
pnpm run build
|
pnpm run build
|
||||||
|
|||||||
@@ -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
|
if [ -n "$tagged_image" ] && [ "$tagged_image" != "$previous_image" ]; then docker image rm "$tag" || true; fi
|
||||||
fi
|
fi
|
||||||
done < <(docker image ls --format '{{.Repository}}:{{.Tag}}' epicnext-cms)
|
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
|
docker image prune -f --filter "until=168h" || true
|
||||||
@@ -27,7 +27,7 @@ it("preserves production runtime configuration and recent cache", () => {
|
|||||||
expect(deploy).toContain("/app/storage");
|
expect(deploy).toContain("/app/storage");
|
||||||
expect(deploy).not.toContain("--env-file");
|
expect(deploy).not.toContain("--env-file");
|
||||||
expect(deploy).toContain(
|
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");
|
expect(deploy).not.toContain("docker volume prune");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user