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.
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
Open the Actions log for the Docker build step and search for the word
CACHEDnext to each instruction; its absence on every layer means nothing was restored.Run
grep -rn 'docker/build-push-action' .github/workflows/and check whethercache-from: type=ghaandcache-to: type=gha,mode=maxare both present on the same step.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.Run
grep -rnE 'image:\s*[a-zA-Z0-9_./-]+(:latest)?\s*$' docker-compose*.yml .github/workflows/*.ymlto find unpinned image tags that would defeat caching even if it were configured.Run
grep -rl 'on:' .github/workflows/*.ymland diff the build commands across workflows that trigger on the same push, to rule out the same image being built twice.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.
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.
Fix: Docker layer cachingConfirm it
Run
grep -rn 'docker/build-push-action' .github/workflows/and check each match forcache-fromandcache-to. Also rungrep -rn 'docker build\|docker compose build' .github/workflows/to catch a plain invocation with no caching flags at all.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.
Fix: Docker build hygieneConfirm it
Run
grep -rn 'docker compose up' .github/workflows/and check whether a service name follows it. Rungrep -rnE 'image:\s*[a-zA-Z0-9_./-]+(:latest)?\s*$' docker-compose*.yml .github/workflows/*.ymlto find images with no tag or an explicit:latest.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.
Fix: Duplicate compilationConfirm it
Run
grep -rl 'on:' .github/workflows/*.ymland check each file'son.pushoron.pull_requestblock for the same branches, then compare each workflow's build or Dockerfile build-stage commands for the same commit.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.
Fix: Shallow checkoutConfirm it
Run
grep -rn 'fetch-depth' .github/workflows/and check whether any job feeding into the Docker build setsfetch-depth: 0without a reason (changelog generation, git blame, tag history) that actually needs it.
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.
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.
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.
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.
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)
Last updated 2026-08-31