---
id: platform/build-cache
title: Build caching
description: Understand when a Bex build reuses cached layers, what invalidates the cache, and what caching never changes.
keywords: [bex, build cache, docker layers, buildkit, clear cache, deploys]
last_updated: 2026-09-23
---

Will your next build be faster, and why did your change not ship? When build
caching is enabled on your instance, an unchanged rebuild reuses layers from
your service's previous build instead of redoing every step — and when a
rebuild surprises you, the cause is usually invalidation (or its absence),
not the cache rewriting your code.

:::note Instance-configured
Build caching is enabled per instance by the operator and may be withdrawn
automatically; do not assume it is on. Everything below describes how caching
behaves where it is enabled.
:::

## What is cached

Each service has its own layer cache in the instance registry, holding the
layers from that service's previous build. The cache is rolling: only the
latest generation is kept, so there is no history to browse or prune, and one
service's layers are never visible to another.

A build whose cache is warm restores those layers before running, so steps
whose inputs are unchanged show as `CACHED` in the build log instead of
re-executing. A first build, or one whose cache is missing, simply builds
everything — a missing or corrupt cache never fails a build.

## When a build imports the cache {#when-a-build-imports}

A normal deploy on an enabled instance imports the service's stored layers and
then exports a fresh cache from the layers it just built, keeping the rolling
generation current. Two cases skip the import:

- A deploy requested with cache clearing rebuilds every layer and still
  exports a fresh cache afterwards. See [clearing the cache](./how-deploys-work.md).
- With caching disabled on the instance, builds run exactly as if the feature
  did not exist: no restore, no export, and cache-clearing requests change
  nothing.

## What invalidates the cache {#invalidation}

| Trigger | What happens |
| --- | --- |
| New source commit | Changed files rebuild from the first affected layer down |
| Dockerfile or build context change | Edited instructions and everything after them re-execute (stock BuildKit layer semantics) |
| Native build environment values change | The operator tracks an opaque revision of the effective environment and folds it into the env-dependent build step, so a values-only change re-runs that step even at a fixed commit |
| Base image update | A new base digest invalidates every layer above it |
| Cache clearing requested | The deploy skips the import and rebuilds all layers |

Secret contents alone never invalidate a layer: BuildKit does not fold secret
mounts into cache keys, which is why the native environment needs its explicit
revision above. Raw secret values never appear in the generated build steps.

## Bounds

The cache is bounded so it can only ever cost time, never correctness or a
failed deploy. Cache transfers carry their own timeout far below the build's
deadline, so a hung registry transfer costs minutes rather than the build.
Cache volumes are size-bounded, and only one rolling generation exists per
service — there is no unbounded growth to manage and nothing for you to clean
up besides requesting a clear rebuild.

## What caching never changes {#never-changes}

- **The source built.** Every build clones fresh; the cache holds layers, not
  source checkouts.
- **Runtime environment values.** The cache wraps the build only. What your
  running container sees still comes from the pushed image plus its own
  environment.
- **Image-backed deploys.** Deploying a prebuilt image runs no build, so
  there is nothing to cache or clear.
- **Build outcomes.** A cache hit reproduces exactly the image the cache was
  built from. Caching affects speed, never results.

## Observing and clearing

Your build log is the only cache signal: reused steps print `CACHED`, and a
failed best-effort cache step surfaces as a `bex: build cache step failed`
line without failing the build. Deploy responses carry no cache fields, and
there is no API that reports whether caching is on — if every step rebuilds,
the instance may simply have it off.

To force a clean rebuild, clear the cache for one deploy from the CLI,
REST, GraphQL, or the dashboard; the next deploy after that reuses the fresh
cache normally. See [How deploys work](./how-deploys-work.md#build-cache)
for the clearing paths and [Docker deploys](./docker-deploys.md) for
stale-artifact recovery. Source pin: Bex `6f2975248`.
