Why your Docker build rebuilds from scratch on every CI run

A Docker build step that rebuilds from scratch almost always means BuildKit has no cache to restore from: docker/build-push-action ran without cache-from and cache-to, so every layer, including the base image pull and dependency install, re-executes on an empty runner. Wire in the GitHub Actions cache backend before looking anywhere else.

static · checkable from the repo
How StarSling works

AI agents find the cause

StarSling agents run this diagnosis against your workflows and run history, identify which cause is yours, and open the fix as a reviewable PR.

Symptoms

GitHub-hosted runners are ephemeral, so the local Docker layer cache that makes a build fast on a laptop does not exist at the start of a run. If docker/build-push-action is not told where to read and write a cache, BuildKit builds every instruction in the Dockerfile from zero, every time, regardless of whether the app code, the base image, or nothing at all changed since the last run. That is the first thing to rule out because it is also the most common: the action defaults to no cache unless cache-from and cache-to are set explicitly. If caching is already wired in and the build is still slow, the next places to look are containers or compile steps that do not belong on the critical path at all, and a checkout step that spends time on git history the build never reads. The four causes below are ordered by how often each one turns out to be the actual reason, so work through them in this order rather than jumping to the last one.

  • The Docker build step takes roughly the same amount of time whether or not the Dockerfile or application code changed since the last run.

  • The Actions log shows every instruction in the Dockerfile executing, including FROM, apt-get or apk installs, and dependency installs that did not change.

  • There is no line in the build log mentioning CACHED or importing a cache manifest before the layers run.

  • The workflow uses docker/build-push-action or a plain docker build command with no reference to a cache location.

  • Two workflows on the same push each build the same image from the same Dockerfile, and both take the full build time.

How to diagnose it

  1. Open the Actions log for the Docker build step and search for the word CACHED next to each instruction; its absence on every layer means nothing was restored.

  2. Run grep -rn 'docker/build-push-action' .github/workflows/ and check whether cache-from: type=gha and cache-to: type=gha,mode=max are both present on the same step.

  3. Run grep -rn 'docker build\|docker compose build\|docker compose up' .github/workflows/ to catch a plain build invocation or a compose command with no service argument.

  4. Run grep -rnE 'image:\s*[a-zA-Z0-9_./-]+(:latest)?\s*$' docker-compose*.yml .github/workflows/*.yml to find unpinned image tags that would defeat caching even if it were configured.

  5. Run grep -rl 'on:' .github/workflows/*.yml and diff the build commands across workflows that trigger on the same push, to rule out the same image being built twice.

  6. Run grep -rn 'fetch-depth' .github/workflows/ on the job that runs the Docker build to rule out a full-history checkout inflating the total job time.

Likely causes

Ordered by how often each one turns out to be the answer. Confirm a cause with its check before you change anything.

  1. The build has no cache backend configured

    docker/build-push-action builds with BuildKit, but BuildKit only reuses layers from a cache it is explicitly pointed at. Without cache-from and cache-to set to type=gha, there is nothing to import at the start of the build and nothing gets exported at the end, so a runner that never saw this repository before builds every layer and the next run starts from the same empty state. A plain docker build or docker compose build invocation has the same gap: neither flag exists to opt in to persistent caching on its own.

    Confirm it

    Run grep -rn 'docker/build-push-action' .github/workflows/ and check each match for cache-from and cache-to. Also run grep -rn 'docker build\|docker compose build' .github/workflows/ to catch a plain invocation with no caching flags at all.

    Fix: Docker layer caching
  2. Compose starts every service and pulls floating image tags

    A docker-compose.yml written for local development usually lists every service the app can run against, so a CI job that runs docker compose up with no service argument starts all of them even when the job only connects to one. Floating tags compound this: an image reference with no tag or :latest resolves to whatever the registry currently serves, so the exact image pulled can differ between runs, and a cache keyed on the image reference cannot tell an old :latest from a new one.

    Confirm it

    Run grep -rn 'docker compose up' .github/workflows/ and check whether a service name follows it. Run grep -rnE 'image:\s*[a-zA-Z0-9_./-]+(:latest)?\s*$' docker-compose*.yml .github/workflows/*.yml to find images with no tag or an explicit :latest.

    Fix: Docker build hygiene
  3. The same compile runs again inside or alongside the Docker build

    If the Dockerfile's build stage recompiles source that a separate CI job already built, or if the same commit triggers more than one workflow that each build the image from scratch, the Docker step is paying for a compile that already happened elsewhere on the same push. That extra work looks identical to a caching gap in the log, but no cache configuration fixes it: the fix is to stop running the build twice, not to cache the duplicate.

    Confirm it

    Run grep -rl 'on:' .github/workflows/*.yml and check each file's on.push or on.pull_request block for the same branches, then compare each workflow's build or Dockerfile build-stage commands for the same commit.

    Fix: Duplicate compilation
  4. The checkout step fetches full history the build never reads

    A Dockerfile build stage that only needs the working tree at HEAD does not benefit from fetch-depth: 0. When actions/checkout pulls the full git history before the Docker build even starts, that time shows up in the job's total wall clock even though it is not part of the image build itself, and it is easy to mistake for the Docker step being slow.

    Confirm it

    Run grep -rn 'fetch-depth' .github/workflows/ and check whether any job feeding into the Docker build sets fetch-depth: 0 without a reason (changelog generation, git blame, tag history) that actually needs it.

    Fix: Shallow checkout

Hand it to an agent

Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to run this diagnosis against your repository and report which cause it found.

Prompt for your coding agent
Diagnose why the Docker image build step in this repository's CI rebuilds from scratch on every run. Work through these checks in order and stop at the first one that matches, then report which cause you found before changing anything.

1. Run `grep -rn 'docker/build-push-action' .github/workflows/` and check each match for `cache-from` and `cache-to` set to `type=gha`. Also check for a plain `docker build` or `docker compose build` with no cache flags.
2. Run `grep -rn 'docker compose up' .github/workflows/` for missing service arguments, and `grep -rnE 'image:\s*[a-zA-Z0-9_./-]+(:latest)?\s*$' docker-compose*.yml .github/workflows/*.yml` for unpinned tags.
3. Compare build commands across workflows triggered on the same push to check for the same image being built twice.
4. Run `grep -rn 'fetch-depth' .github/workflows/` on the job that runs the Docker build.

Report the cause you found, the exact file and line, and wait for confirmation before editing any workflow file.

Go further

Find the cause, fix them all, or keep them fixed.

You have the prompt that works out which cause applies. Here is how much further you can take it, each step doing more for you than the last.

  1. Find the cause

    Copy the diagnosis prompt above

    Hand it to your coding agent to confirm which cause applies in your repo. It reports back what it found and changes nothing.

  2. Fix them all, once

    Install the ci-speedup skill

    One prompt audits your whole repo against all 73 ci-speedup patterns (these 6 plus 67 more) and hands your agent every fix at once. Open source, MIT, runs locally.

  3. Keep it fixed, forever

    Install the StarSling GitHub App

    Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.

Sources

1Docker docs: GitHub Actions cache backend for BuildKit (opens in new tab)

2Docker docs: cache storage backends overview (opens in new tab)

3docker/build-push-action on GitHub (opens in new tab)

4actions/checkout on GitHub (opens in new tab)

Last updated 2026-08-31