Skip to content

Build caches

A build reuses the layers it finds in its builder’s cache, and the builder lives on one machine. Your laptop keeps its layers between builds. A CI runner starts empty every time, so without help it reruns every step of the Dockerfile on every deploy. A remote cache stores the layers somewhere every builder can read them, such as the registry next to the image.

your laptopsecond buildreusedCOPY srcfresh CI runnerno cacheFROMRUN bun installCOPY srcfresh CI runnercache: truepull :buildcacheCOPY srcpush :buildcachebuild startsyour laptopsecond buildreusedCOPY srcfresh CI runnerno cacheFROMRUN bun installCOPY srcfresh CI runnercache: truepull :buildcachepush :buildcachebuild starts
The same change to src, built three ways. A fresh runner reruns the install. With cache: true it pulls the unchanged layers from the registry, builds the one step that changed, and saves the layers for the next run.
alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Docker from "@alchemy.run/docker";
import * as Config from "effect/Config";
import * as Effect from "effect/Effect";
export default Alchemy.Stack(
"MyApp",
{
providers: Docker.providers(),
state: Alchemy.localState(),
},
Effect.gen(function* () {
const image = yield* Docker.Image("api", {
name: "acme/api",
build: { context: "./api", cache: true },
registry: {
server: "ghcr.io",
username: "acme",
password: yield* Config.Redacted("GITHUB_TOKEN"),
},
});
return { ref: image.imageRef };
}),
);

cache: true keeps the layers at the image repository’s :buildcache tag, here ghcr.io/acme/api:buildcache, using the image’s registry credentials. It keeps every stage’s layers, not only the final image’s, so a multi-stage build skips its build stage too.

For any other BuildKit cache backend, pass its --cache-from and --cache-to values. In GitHub Actions:

const image = yield* Docker.Image("api", {
name: "acme/api",
build: {
context: "./api",
cache: {
from: ["type=gha,scope=api"],
to: ["type=gha,scope=api,mode=max"],
},
},
});

Backends like gha and s3 take their credentials from the environment. These values appear on the docker command line, so never put a secret in them.

Saving a cache needs a builder that can export one: a docker-container Buildx builder, which docker/setup-buildx-action creates in GitHub Actions, or the default builder with the containerd image store turned on.

The cache never changes the image itself, so turning it on or off doesn’t rebuild or redeploy anything.

Every resource that builds an image takes the same cache option: Cloudflare Containers, ECS, Cloud Run and Kubernetes workloads under build, and AWS.ECR.Image and Lambda container images next to buildArgs.