Skip to content
Sengi

Docker layer caching on Sengi runners

How do I cache Docker layers on Sengi?

Build with docker/build-push-action and set cache-from: type=gha and cache-to: type=gha,mode=max. On a Sengi runner that cache is served from Sengi's own cache, and the next run restores every unchanged layer instead of rebuilding it: 1.9× faster in our measured build. Your actions/cache steps and the setup actions' caches use the same cache with no workflow changes. All of it is included free.

Last checked September 30, 2026, on Sengi runners, including the measured cold and warm build below.

What does Sengi's cache do?

Every Sengi runner is wired to Sengi's own GitHub Actions cache. Anything that talks to the Actions cache uses it with no configuration change:

What your workflow usesWhat happens on SengiChange needed
actions/cacheSaves and restores from Sengi's cache, with exact and restore-keys matchesNone
setup-node, setup-python and other setup actions with cache:Package-manager caches save and restore from Sengi's cacheNone
docker/build-push-action with type=ghaBuild layers are exported to Sengi's cache and imported on the next runAdd cache-from and cache-to if the workflow does not already have them

A warm actions/cache restore on Sengi brings a node_modules directory back exactly as it was saved, so the job skips npm ci altogether. Docker builds get the same benefit layer by layer: a layer whose inputs have not changed comes back from the cache instead of running its RUN step again.

What does the workflow look like?

Copy this job into a workflow. It builds the Dockerfile at the repository root on a 4 vCPU Sengi runner and keeps every layer between runs:

jobs:
  build:
    runs-on: sengi-standard-4-ubuntu-2404
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          tags: my-app:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

docker/setup-buildx-action creates the BuildKit builder that can export a cache; the default docker driver cannot. cache-from: type=gha imports layers saved by earlier runs, and cache-to: type=gha,mode=max saves this run's layers when the build finishes. To publish the image, log in to your registry and set push: true; the cache settings stay the same.

If your workflow already uses type=gha, it needs no change beyond runs-on. The same lines that used GitHub's cache on a GitHub-hosted runner use Sengi's cache on a Sengi runner.

Should I use mode=max or mode=min?

Use mode=max for most builds. The mode decides which layers cache-to saves:

ModeWhat it savesBest for
mode=min (the default)Only the layers in the final imageSingle-stage Dockerfiles where every layer ends up in the image
mode=maxEvery layer of every stage, including build stages that are not in the final imageMulti-stage builds, where the expensive work happens in a builder stage

With mode=min, a multi-stage build caches only its small final stage, so the compile or install stage runs again on every build. mode=max saves more data per run, and in return the builder stage comes back from the cache too.

How do I know the cache was used?

Open the build step's log. A layer restored from the cache is marked CACHED:

#8 [3/6] COPY package.json package-lock.json ./
#8 CACHED

#9 [4/6] RUN npm ci
#9 CACHED

Near the start of the step, BuildKit also logs importing cache manifest from gha:…, which shows it found cache from an earlier run. A layer that ran again shows its commands' output and a duration instead of CACHED.

Then compare the step's duration with the run before it. A cold build pays for every layer; a warm build pays only for the layers after the first change.

RunWhat changednpm ci layerBuild stepWhole job
ColdFirst build, empty cacheRan (11 s)68 s92 s
WarmOne source file editedCACHED36 s58 s

Measured September 30, 2026 on sengi-standard-4-ubuntu-2404 with the workflow above and a Node.js and TypeScript image. The warm build restored every layer up to the source copy, reran only the TypeScript build, and ran the build step 1.9× faster. The longer your dependency and compile layers, the bigger the win.

The runner itself is faster too. On RunsOn’s open benchmark harness, run on Sengi October 9, 2026, PostHog’s production Docker image built 1.9× faster than on GitHub’s runner, at 74% less per build: 7m34s and $0.045 on a 4 vCPU Sengi runner, against 14m36s and $0.175 in RunsOn’s published GitHub result.

How do I keep a separate cache per branch?

Add scope= to both cache settings. Each scope is its own cache, so two branches building different dependency sets stop replacing each other's layers:

      - uses: docker/build-push-action@v6
        with:
          context: .
          cache-from: |
            type=gha,scope=${{ github.ref_name }}
            type=gha,scope=main
          cache-to: type=gha,mode=max,scope=${{ github.ref_name }}

The second cache-from line lets a new branch start from main's layers on its first build. Use a scope per image as well when one workflow builds several images, for example scope=api and scope=worker, so each image keeps its own cache.

How should I write the Dockerfile for caching?

A layer is reused only if it and every layer before it are unchanged, so order the Dockerfile from what changes least to what changes most:

  • Dependencies before source. Copy the manifest and lockfile, install, then copy the rest of the source. A code change then rebuilds only the layers after the install.
  • Multi-stage builds. Compile or install in a builder stage and copy only the output into a slim final stage. Combine this with mode=max so the builder stage is cached too.
  • A .dockerignore. Leave out .git, local build output and logs, so files that do not affect the image cannot invalidate the COPY layer.
FROM node:22-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Here a change to application code reuses the base image and the npm ci layer, and reruns only the copy and the build.

What starts fresh on every job?

Every Sengi job runs in its own fresh VM, so every build starts from the same clean, known machine and no state leaks from one job into the next. The local Docker daemon starts clean, and workspace files outside a cache step go with the VM.

What carries over is what you cache: layers exported with cache-to: type=gha and paths saved by actions/cache or a setup action.

Frequently asked questions

Do I need to change my workflow to use Sengi's cache?

No. actions/cache, the cache built into setup-node, setup-python and the other setup actions, and Docker builds with type=gha all use Sengi's cache automatically once the job runs on a Sengi runner. The runs-on line is the only change.

Does Docker layer caching cost extra on Sengi?

No. The Actions cache and Docker layer caching through type=gha are included with every Sengi runner, with no added cache charge. You pay only for the runner minutes your jobs use.

How fast are warm builds?

In our measured build, the build step went from 68 s cold to 36 s warm, 1.9× faster. The first build on Sengi fills the cache; every build after it restores unchanged layers instead of rebuilding them.

How long does Sengi keep cached layers?

A cache entry that no job has read for seven days is removed, so layers your builds keep using stay available. When a repository's cache reaches its storage limit, the oldest entries are removed first and the next build saves fresh ones.

Can pull requests read the cache?

Yes. A job reads caches saved for its own branch first, then the default branch. A pull request can restore the default branch's layers, and what it saves stays with the pull request instead of being served to other branches.

Does the Docker daemon keep images between jobs?

Every job runs in a fresh VM with a clean Docker daemon, so nothing leaks between jobs. The layer cache is what persists: type=gha stores it outside the VM, and the next job restores it.

For more ways to shorten a workflow, read how to speed up GitHub Actions. To switch a job to Sengi, see how switching works and Sengi runner pricing.

Cut your GitHub Actions bill by up to 74%.

Up to half GitHub’s price per minute, on 5 GHz bare metal. Builds finish up to 1.9× faster than on GitHub’s runners, so together you pay up to 74% less per build. Change one runs-on line, or give your coding agent one prompt and it opens the pull request for you.

Start free

3,000 free minutes a month, no card. See how switching works