Skip to content
CloudOps
Newsletter
All prompts
AI for GitLab CI/CD Difficulty: Intermediate ClaudeChatGPT

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

  1. Enable BuildKit explicitly in CI; don’t rely on default.
  2. Set up registry cache for cross-pipeline reuse.
  3. For multi-arch, use native runners or accept QEMU overhead.
  4. 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_modules from host; slow upload.
  • Cache image grows unbounded → push with mode=min to 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

Newsletter

Free: the DevOps AI Incident-Triage Cheat Sheet

Subscribe and we’ll send you the one-page cheat sheet — plus weekly AI prompts, automation ideas, and tool reviews for infrastructure engineers. One email a week. No spam, unsubscribe anytime.

  • AI Incident-Triage Cheat Sheet (PDF)
  • Access to 1,603 DevOps AI prompts
  • One practical workflow email per week