CalcSnippets Search
DevOps 3 min read

`docker buildx --cache-to` and `--cache-from` Are the Flags You Need When CI Keeps Rebuilding the World

A practical guide to Docker Buildx cache configuration for developers who want faster image builds in CI instead of re-downloading and re-compiling everything on every run.

Why this matters: a surprising number of slow CI pipelines are not slow because your code is huge. They are slow because your build cache strategy is nonexistent.

Docker Buildx supports explicit cache import and export with --cache-from and --cache-to. If your CI keeps rebuilding dependencies, re-running expensive package install layers, or recompiling the same stack over and over, this is one of the first places to look.

The problem with default assumptions

A lot of teams assume Docker caching will “just happen.” That is only partially true. Local builds often benefit from local layer cache, but CI runners are usually ephemeral. That means each run starts colder than developers expect.

If you do not explicitly export cache somewhere reusable, then every fresh runner behaves like it has never seen your project before.

That gets expensive fast.

The basic pattern

Buildx lets you import cache:

docker buildx build \
  --cache-from type=registry,ref=ghcr.io/example/app:buildcache \
  --cache-to type=registry,ref=ghcr.io/example/app:buildcache,mode=max \
  -t ghcr.io/example/app:latest .

The exact backend can vary, but the idea stays the same:

  1. pull prior cache
  2. build
  3. push updated cache

That is how you stop every CI run from behaving like day one.

Why registry-backed cache is common

When your runner is disposable, a remote cache destination is often the most practical choice. A registry-backed cache works well because it travels with your pipeline and does not depend on the runner’s local disk surviving.

This is especially useful for:

  1. dependency-heavy Node images
  2. Python images with compiled packages
  3. multi-stage builds
  4. polyglot stacks with expensive package layers

Layer order still matters

Caching flags do not rescue a badly ordered Dockerfile.

You still want to:

  1. copy dependency manifests early
  2. install dependencies before copying frequently changing app code
  3. keep the most stable expensive layers higher up

Example idea:

FROM node:22-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

If you copy the whole repo before dependency install, you invalidate more cache than necessary.

Good Dockerfiles and Buildx cache settings work together.

Why this is often the biggest CI win per minute spent

Developers love deep CI refactors, but sometimes the highest ROI move is simpler:

  1. export cache
  2. import cache
  3. fix layer ordering

That can cut build time dramatically without changing your application at all.

When to use it

Reach for Buildx cache controls when:

  1. your image builds are expensive
  2. runners are ephemeral
  3. local builds are much faster than CI
  4. package installation dominates pipeline time

Those symptoms nearly always justify checking cache strategy before blaming the app itself.

Final recommendation

If CI keeps rebuilding the world, stop assuming Docker cache is automatic in ephemeral runners. Use Buildx with explicit --cache-from and --cache-to, keep your Dockerfile cache-friendly, and treat build cache as part of pipeline design rather than as a happy accident.

Sources

Keep reading

Related guides