GitLab CI/CD with Docker BuildKit & Cache Optimization Prompt
Use BuildKit in GitLab CI/CD — multi-stage caching, registry cache, build secrets, multi-arch images, and avoiding the cold cache trap.
- Target user
- DevOps engineers building Docker images in GitLab CI
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior DevOps engineer who builds container images in GitLab CI at scale — multi-stage Dockerfiles, BuildKit cache exports, multi-arch via QEMU/Buildx, build secrets without leaking.
I will provide:
- The Dockerfile (or a description)
- Current build approach (`docker build` legacy / BuildKit / buildx)
- Image size and build time
- Multi-arch needs (amd64/arm64)
- The goal: speed up / multi-arch / inject secrets / smaller image
Your job:
1. **Use BuildKit / Buildx**:
- Set `DOCKER_BUILDKIT=1` (legacy) or use `docker buildx`
- BuildKit is the default in Docker 23+
- For GitLab DinD (Docker-in-Docker), enable BuildKit explicitly
2. **For multi-stage caching**:
- BuildKit caches each stage independently
- `--cache-from` / `--cache-to` allow exporting/importing the cache
- Storage targets: `registry` (push cache to registry as image), `local` (filesystem), `gha` (GitHub Actions)
3. **For registry cache** (most useful in CI):
- `--cache-to type=registry,ref=$CI_REGISTRY_IMAGE/cache:buildcache,mode=max`
- `--cache-from type=registry,ref=$CI_REGISTRY_IMAGE/cache:buildcache`
- `mode=max` exports all stages; `mode=min` only the final
- Branch-specific cache: `cache:$CI_COMMIT_REF_SLUG`
4. **For multi-arch (amd64 + arm64)**:
- `docker buildx create --use --name multiarch` to set up builder
- `--platform linux/amd64,linux/arm64` builds for both
- QEMU emulation slow; native arm64 runner faster (use Karpenter/Fleeting for arm64 nodes)
5. **For build secrets**:
- `--secret id=mysecret,src=/path/to/secret` exposes during specific RUN
- In Dockerfile: `RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret`
- **Never** `ARG SECRET=$SECRET` — leaks into image history
6. **For SSH-based git clones during build**:
- `--ssh default` forwards SSH agent into build
- In Dockerfile: `RUN --mount=type=ssh git clone git@...`
7. **For image size optimization**:
- Multi-stage: builder + runtime; runtime has only needed
- Distroless or alpine for runtime
- Layer ordering: most-changed last
- `.dockerignore` to exclude build context bloat
8. **For GitLab dependency proxy**:
- `CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX` avoids Docker Hub rate limits
- `FROM ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:20`
Mark DESTRUCTIVE: putting secrets in build ARGs (leaks to image history), single-stage build for production (huge image), pushing arm64 emulation builds to production at scale (slow QEMU compile).
---
Dockerfile (summary): [DESCRIBE — base, stages, slow parts]
Current build approach: [legacy / BuildKit / buildx]
Image size + build time: [DESCRIBE]
Multi-arch needs: [amd64 only / amd64+arm64]
Goal: [speed / multi-arch / secrets / size]
Why this prompt works
BuildKit + caching dramatically speeds CI image builds, but most teams use legacy docker build and don’t realize the gap. Multi-arch and secrets are the next pitfalls.
How to use it
- Enable BuildKit explicitly in CI; don’t rely on default.
- Set up registry cache for cross-pipeline reuse.
- For multi-arch, use native runners or accept QEMU overhead.
- For secrets, use
--secret, never ARG.
Useful commands
# Verify BuildKit available
docker buildx version
# Single-platform build with registry cache
docker buildx build \
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE/cache:buildcache" \
--cache-to type=registry,ref="$CI_REGISTRY_IMAGE/cache:buildcache",mode=max \
--push \
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" \
.
# Multi-platform build
docker buildx create --use --name multiarch
docker buildx build \
--platform linux/amd64,linux/arm64 \
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE/cache:buildcache" \
--cache-to type=registry,ref="$CI_REGISTRY_IMAGE/cache:buildcache",mode=max \
--push \
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" \
.
# With build secret
echo "$NPM_TOKEN" > /tmp/npm-token
docker buildx build \
--secret id=npm-token,src=/tmp/npm-token \
--tag "$IMAGE" \
.
# Inspect layers / cache hits
docker history "$IMAGE"
GitLab CI patterns
Standard BuildKit with DinD
variables:
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_BUILDKIT: "1"
build:
image: docker:26.1.4
services:
- name: docker:26.1.4-dind
alias: docker
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker buildx create --use --name builder
script:
- |
docker buildx build \
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE/cache:$CI_COMMIT_REF_SLUG" \
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE/cache:main" \
--cache-to type=registry,ref="$CI_REGISTRY_IMAGE/cache:$CI_COMMIT_REF_SLUG",mode=max \
--push \
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" \
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" \
.
Multi-arch build (native arm64 runner preferred)
build-multi-arch:
image: docker:26.1.4
services:
- name: docker:26.1.4-dind
alias: docker
before_script:
- docker buildx create --use --name multiarch --driver docker-container
- docker buildx inspect --bootstrap
script:
- |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE/cache:buildcache" \
--cache-to type=registry,ref="$CI_REGISTRY_IMAGE/cache:buildcache",mode=max \
--push \
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" \
.
tags: [amd64-large] # better: tag matrix with one per arch
Build secret pattern
build-with-secret:
image: docker:26.1.4
services:
- name: docker:26.1.4-dind
alias: docker
variables:
DOCKER_BUILDKIT: "1"
script:
- echo "$NPM_TOKEN" > /tmp/npm-token # NPM_TOKEN is masked CI variable
- |
docker buildx build \
--secret id=npm-token,src=/tmp/npm-token \
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" \
--push \
.
- rm -f /tmp/npm-token
# Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npm-token \
NPM_TOKEN=$(cat /run/secrets/npm-token) \
npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN && \
npm ci
FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["node", "server.js"]
Dependency proxy (avoid Docker Hub rate limit)
variables:
CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX: "$CI_SERVER_HOST/<group>/dependency_proxy/containers"
build:
image: docker:26.1.4
script:
- |
docker buildx build \
--build-arg BASE_IMAGE="$CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX/node:20-alpine" \
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" \
.
ARG BASE_IMAGE=node:20-alpine
FROM ${BASE_IMAGE}
Hardened multi-stage Dockerfile
# syntax=docker/dockerfile:1.7
ARG GO_VERSION=1.23
ARG ALPINE_VERSION=3.20
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /app
COPY --from=builder /out/app /app/app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app/app"]
Common findings this catches
- No
--cache-from→ no cache hit; every build from scratch. - Build secret as ARG → committed to image history; rotate immediately.
- Multi-arch via QEMU on amd64 runner → slow; consider native arm64 runners.
- Pinning
docker:latest→ may break on Docker version bump. - No
.dockerignore→ build context includes.git,node_modulesfrom host; slow upload. - Cache image grows unbounded → push with
mode=minto limit, or rotate cache tag. - DinD without
--privileged→ fails; required for BuildKit.
When to escalate
- Native arm64 runner provisioning — coordinate with infra; budget.
- Cache backend performance issues — regional placement; latency to registry.
- Build secret leak detected — rotate credential; review process.
Related prompts
-
Dockerfile Security Review Prompt
AI security review of a Dockerfile — privilege, attack surface, secrets in layers, vulnerable bases, supply-chain risk.
-
GitLab CI/CD Pipeline Optimization Prompt
Speed up slow GitLab pipelines — DAG with `needs:`, cache vs artifacts, parallel jobs, image pre-builds, dependency proxy, and shallow clones.
-
Kubernetes ImagePullBackOff Debugging Prompt
Diagnose `ImagePullBackOff` / `ErrImagePull` — wrong image name, private registry auth, imagePullSecrets, image signing/content trust, network reach to the registry.