Inline Cache
Inline cache is Docker build cache metadata embedded in the image you push, so pulling that image also supplies the cache hints for the next build.
Inline cache is Docker build cache metadata that BuildKit embeds inside the image it pushes, so the image and the hints needed to reuse its layers travel as one artifact. A later build imports it by pointing --cache-from at that image reference, and any builder able to pull the image can read the cache.
The limit sits in what the embedded metadata can describe. Inline cache records the layers present in the final image, so work done in a stage that was dropped along the way leaves nothing for the next build to match against.
Definition
Inline cache is one of the cache export backends supported by BuildKit, selected with --cache-to type=inline on the build command or cache-to: type=inline in an action input. Where other backends write the cache manifest to a separate object, inline writes it into the configuration blob of the image the build produces (Docker inline cache backend documentation, checked on 2026-08-13).
Three properties follow from that placement.
- The build has to produce an image. Inline cache is available with the image and registry exporters, so a build that only writes a tarball or a local directory has nowhere to put the metadata.
- There is no second artifact to manage. Retention, storage cost, and access control are whatever the image repository already applies to the tag.
- The recorded set is bounded by the image. A layer that never reaches the final image cannot be described by metadata that lives inside it.
What each cache backend stores
| Backend | Where the cache manifest is written | Cache modes it supports | Extra artifact to manage |
|---|---|---|---|
inline | The configuration blob of the pushed image | min only | None, the image tag is the cache |
registry | A separate manifest at a cache reference you name | min and max | One cache reference plus its storage |
local | A directory on the machine that ran the build | min and max | A directory that has to outlive the build |
gha | The GitHub Actions cache service | min and max | A store with its own size limit and eviction |
The mode column is where inline cache differs in practice. min mode exports the layers of the resulting image. max mode exports the layers produced by every step, including steps in stages that were used and discarded. Inline cache supports min mode alone, so a build that needs the wider record has to write to one of the other backends (Docker cache backend reference, checked on 2026-08-13).
Reading an inline cache back
The import side uses the registry backend, because the metadata sits inside an image in a registry. A build imports it with --cache-from type=registry,ref=<image>, and BuildKit reads the cache records out of the image configuration before deciding which steps to run. The reference has to be one a previous build pushed with inline export, which in most workflows is the image tag for the default branch.
Two consequences are worth planning around. Overwriting the tag replaces the cache, so a floating reference such as :latest holds whatever the last push to it produced. And a pull request that changes an early instruction misses every step downstream of that change, because the digest chain diverges from the recorded one at the first difference.
The build argument form
Before Buildx flags were common, the same behavior was requested through a build argument: docker build --build-arg BUILDKIT_INLINE_CACHE=1 on an engine running BuildKit. That form still works and produces the same embedded metadata. Contents of RUN --mount=type=cache directories stay on the builder under every backend, so a package manager download directory is absent from an inline export the same way it is absent from a registry export.
Example
This Dockerfile compiles in one stage and ships a second, which is the shape where inline cache behaves differently from a dedicated cache export.
# syntax=docker/dockerfile:1
FROM node:22 AS build
WORKDIR /src
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /src/dist /usr/share/nginx/htmlThe workflow below builds it on a GitHub Actions runner, pushes the image, and asks for inline cache on the way out. The next run imports the same reference.
name: image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:latest
cache-to: type=inlineOne docker push moves the image and its cache metadata together. There is no second reference in the registry, and a developer who pulls :latest on a laptop can build against the same cache with the same --cache-from line.
What the second run hits and what it misses
Assume the first run pushed the image and a later push edits one file under src/.
| Step | Recorded by inline export | Second run behavior |
|---|---|---|
COPY package.json package-lock.json ./ | No, the build stage is discarded | Runs again |
RUN npm ci | No, the build stage is discarded | Runs again, downloading the dependency set |
COPY . . and RUN npm run build | No, the build stage is discarded | Runs again |
FROM nginx:1.27-alpine | Yes, the layer is in the final image | Matched from the imported cache |
COPY --from=build /src/dist ... | Yes, the layer is in the final image | Rebuilt, since the copied files changed |
The npm ci line is the cost. It belongs to a stage the final image does not contain, and inline metadata has no place to record a layer that the image never carries. Switching the export to cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=max records the build stage as well, at the price of a separate cache reference, an import download before the build, and an export upload after it. That trade between what the cache describes and what each run pays to move it is the reason both backends exist.
Related Terms
- Registry cache and how a separate cache reference stores build layers: the backend that writes a cache manifest of its own and supports
maxmode. - Choosing a Buildx cache backend on GitHub Actions: how inline, registry, and builder-local caches behave inside a workflow, with the failure signature each one leaves in a job log.
- Image digests and how a build pins the exact image it read: why a floating tag makes an inline cache reference ambiguous across runs.
- Docker inline cache backend documentation: the upstream reference for the export flag, its exporter requirements, and its mode support.
- WarpBuild Docker builders documentation: builder profiles and the actions that point a workflow at a builder.
- WarpBuild caching documentation: cache usage inside GitHub Actions workflows.
- WarpBuild pricing: per minute rates by runner type.
FAQ
What is the difference between inline cache and a registry cache export?
Inline cache writes the cache manifest into the configuration of the image you push, so the image is the cache. A registry cache export writes a separate manifest to its own reference, which can describe every step of the build including stages that never reach the final image. Inline needs no second reference to manage and describes less of the build.
Why does my multi-stage build still rebuild with inline cache?
Inline cache supports min mode only, and min mode records the layers of the resulting image. A builder stage that compiles a binary and hands one file to a slim runtime stage leaves no record behind, so the compile step runs again on the next build while the runtime layers match.
How do I turn inline cache on in a Docker build?
With Buildx, pass cache-to type=inline on a build that pushes an image, then import it on the next build with cache-from type=registry pointed at that image reference. With docker build on an engine running BuildKit, the equivalent is the build argument BUILDKIT_INLINE_CACHE set to 1.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.