The State of Container Builds 2026
The container build is the slowest, most expensive step in modern CI, and on cold hosted runners it throws away its layer cache on every run, which is exactly the waste a warm cache claws back.
Executive summary
For most teams the container build is now the single longest step in the pipeline. As applications absorbed more dependencies, multi-stage Dockerfiles, and security-scan layers, the image build quietly grew into the step everyone waits on. It is rarely the step anyone deliberately invested in making slow; it accreted, one dependency and one stage at a time, until the build that used to take a couple of minutes takes ten, and it gates every merge and every deploy along the way.
The defining problem is structural rather than a matter of any single slow command. On stock hosted runners every job starts on a fresh machine with an empty layer store, so the build throws away its cache on every run. The same layers, the same base image, the same dependency installs, are reconstructed from scratch each time even though their inputs never changed. The build is not slow because the work is hard; it is slow because the work is repeated needlessly on a cold machine.
The encouraging part is that container builds are unusually responsive to a handful of well-understood techniques. BuildKit, ordered layers, a persistent layer cache, and smaller base images compound on each other, and the difference between a team that has wired these up and a team that has not is measured in multiples, not percentages. Unlike test-suite speed, which often requires touching application code, almost all of this is infrastructure and Dockerfile hygiene that leaves the application untouched.
Among those techniques, one dominates: persisting the layer cache close to the runner so the build restores from a warm cache instead of rebuilding cold. A warm BuildKit layer cache makes a representative build several times faster, and it does so by skipping exactly the layers, base image, system packages, dependency installs, that the cold build wastes its minutes reconstructing. Everything else in this report is a complement to that central lever.
This report quantifies where a container build spends its minutes, how much each optimization recovers, and what a build minute actually costs once registry egress and cold-cache rebuilds are priced in. The recurring framing, shared across every Latchkey report, is that the waste here is mechanical and addressable: the slow build and the fast build run the same Dockerfile, and the only difference is whether the runner kept the cache warm.
Estimated split of wall-clock time for a typical multi-stage Docker build on a cold runner. · Source: Latchkey analysis (modeled)
Median build wall-clock for the same image under different cache setups. · Source: Latchkey analysis (modeled)
Email me the report
The full report is right here on this page, free. Want the link in your inbox to read later or share, plus new Latchkey reports as they drop? Drop your email and we will send it over.
Sent! Check your inbox for the report link.
No spam. Unsubscribe anytime.
The cache miss is the default, not the exception
On stock hosted runners every job starts on a fresh machine with an empty layer store, so the build either rehydrates a remote cache over the network or rebuilds from scratch. There is no persistent local cache to hit, because the machine that built the previous image no longer exists. The cache miss is not an occasional bad outcome; it is the structural default of a cold runner, and it is paid on every single build.
The result is that the majority of container builds reconstruct layers whose inputs never changed. The base image is the same, the system packages are the same, the locked dependency set is the same, and yet all of it is rebuilt because the cache that would have restored it was discarded with the previous runner. The work is not new; it is the same work, repeated, because nothing persisted between jobs.
Persisting the layer cache close to the runner is the single change that moves a build from minutes to seconds, and it requires no Dockerfile rewrite. When the cache survives between jobs and lives on local disk, the build restores its unchanged layers at disk speed and spends its time only on the layers that actually changed. This is the foundational finding of the report: the build is slow because the cache is cold, and the fix is to keep it warm.
- A cold hosted runner has no persistent local layer store, so the cache miss is the structural default, not an occasional event.
- Most builds rebuild base-image, system-package, and dependency layers whose inputs never changed.
- Persisting the layer cache locally moves a build from minutes to seconds with no Dockerfile change.
BuildKit is necessary but not sufficient
Enabling BuildKit unlocks parallel stages, build-time mounts, and cache export, and it is the right baseline for any modern build. Its parallel layer graph builds independent stages concurrently, its mount caches let a package manager keep its download cache across builds, and its cache export embeds the metadata a later build needs to reuse layers. No serious container pipeline should be running the legacy builder, and most are not.
But BuildKit still needs somewhere durable to put the cache. The cache export is only useful if there is a fast, warm place to export to and import from, and on a cold ephemeral runner that place is usually a remote store reached over the network. Teams that turn on BuildKit and then export the cache to a slow remote store often see modest gains, because every job still pays to pull and import the cache before it can start restoring layers from it.
The same BuildKit setup against a warm, local layer cache is where the large multiples appear. When the cache is already on local disk, BuildKit skips the network transport entirely and restores layers directly, and the build that took close to ten minutes cold finishes in under two. The breakdown of build time by caching strategy, shown below, makes the gradient clear: each step toward a warmer, more local cache recovers more time, and the warm local cache is the floor.
Image size is a recurring tax, not a one-time cost
A large base image is paid for on every pull, in every job, across the whole fleet, every day. The size of the image is not a one-time penalty paid when it is built; it is a recurring transport cost paid every time the image is pulled, whether for a build, a scan, or a deploy. At fleet scale, where dozens of runners pull images all day, a bloated base image quietly fans out into a large and continuous registry-egress bill.
Moving from a full distro base to a slim or distroless base routinely cuts compressed size by half or more, which shrinks both push time and the registry-pull minutes that fan out across the fleet. The chart of image size by base, shown below, translates the compressed size into the pull minutes it adds across a representative fleet per day, which is the number that actually appears on the bill. The static or scratch base is a fraction of the full distro, and that fraction recurs on every pull.
The savings are unglamorous but they compound at fleet scale. Shrinking the base image is not a heroic optimization; it is choosing a smaller starting point and not installing what the runtime does not need. But because the cost is recurring and fleet-wide, a one-time decision to use a slim base pays back continuously, and it stacks cleanly on top of layer caching: a smaller image is both faster to pull and faster to cache.
Compressed image size by base, and the registry-pull minutes it adds across a 50-runner fleet per day. · Source: Latchkey analysis (modeled)
Layer ordering is free performance
Most slow builds invalidate their dependency-install layer by copying the whole source tree before installing dependencies. Docker's layer cache is invalidated by the first instruction whose inputs change, and if the build copies all source before running the install, then any source change, even a one-line edit to application code, invalidates the copy and forces the expensive dependency install to run again from scratch.
Copying only the manifest files first, installing, then copying the rest of the source keeps the expensive layer cached across the common case where only application code changed. Because the manifest files change far less often than the code, the dependency-install layer stays valid through the great majority of commits, and the build skips straight to the cheap final copy. It is a two-line reordering that pays back on nearly every build.
This is the cheapest optimization in the entire report and one of the most commonly missed. It costs nothing, requires no new infrastructure, and breaks nothing, yet a surprising number of Dockerfiles still copy source before installing dependencies and pay the full install cost on every code change. Combined with a persistent layer cache, correct ordering means the expensive layers are both cacheable and actually cached, which is the whole point.
- Docker invalidates the layer cache at the first instruction whose inputs change, so copying all source before installing forces a reinstall on any code edit.
- Copying manifest files and installing dependencies first keeps the expensive layer cached across the common code-only change.
- Manifest files change far less often than code, so the dependency-install layer stays valid through most commits.
- It is a two-line reordering that costs nothing and pays back on nearly every build.
The security scan is the newest fixed cost in the build
Container image scanning has moved from optional to expected, and most pipelines now run a vulnerability scan on the built image before it is allowed to ship. That is a clear security win, but it adds a fixed cost to every build, and it is a cost that grows with image size because the scanner has to inspect every layer and every package the image contains. A bloated image is slower to build, slower to pull, and slower to scan.
The scan also interacts with caching in a way teams often miss: a smaller, slimmer image with fewer system packages has less to scan and fewer potential findings to triage, so shrinking the base image pays a second dividend on the scan stage. The distroless and static bases that cut pull cost also cut scan time and reduce the attack surface the scan reports, which is why image-size hygiene and security posture point in the same direction.
The practical lesson is that the scan stage rewards the same discipline as everything else in this report: a small, well-layered image scans faster and reports less noise than a large one. As scanning becomes universal and policy gates start blocking merges on scan results, the teams with lean images spend less time both running the scan and triaging its findings, while teams on full distro bases pay a growing scan tax on every build.
Container build time hides on the critical path of every deploy
Because the container build sits between a passing test suite and a running deploy, its latency is paid on every release, not just on builds that change the image meaningfully. A one-line configuration change that triggers a full cold rebuild pays the same multi-minute build cost as a change that rewrote half the application, because the cold runner has no way to know the base and dependencies are identical to last time.
The build also tends to be the step least scrutinized for speed, precisely because it is infrastructure rather than code. Teams will profile a slow test, but a slow build is often accepted as just how long building takes, even though it is usually the most recoverable latency in the pipeline. The cold build's minutes are not spent on anything irreducible; they are spent rebuilding cached-away layers, which is the most addressable kind of slow there is.
The throughline, consistent across every Latchkey report, is that this waste is mechanical and lives in the runner rather than in the application. The slow build and the fast build run the identical Dockerfile; the only difference is whether the runner kept a warm layer cache the build could restore from. That is what makes container-build latency such a high-return target: the fix changes the infrastructure, not the image, and the image keeps shipping exactly as before, only faster.
The economics favor a warm managed cache
Once registry egress and repeated cold rebuilds are priced in, a container build on a stock hosted runner is one of the most expensive things a pipeline does. It burns runner minutes rebuilding cached-away layers, it burns registry-egress minutes pulling oversized images across the fleet, and on larger runners it burns those minutes at a higher per-minute rate. The cost is the product of wasted time and the rate paid for that time, and the cold hosted build maximizes both.
A managed runner with a persistent layer cache captures most of the time savings and most of the cost savings at once. The warm cache cuts the minutes the build burns by restoring unchanged layers instead of rebuilding them, and the managed per-minute rate cuts the price of each remaining minute, so the two effects multiply. The chart of cost per container build, shown below, puts the cold hosted build against a managed warm-cache build, and the gap is a multiple, not a margin.
This is why container-heavy teams see the clearest return from moving the build leg onto managed infrastructure. A pipeline dominated by tests has its cost spread across work that is genuinely necessary; a pipeline dominated by container builds has its cost concentrated in repeated, cacheable work, which is exactly the kind of waste a warm managed cache removes. The more your pipeline is a build, the more a warm cache is worth.
Modeled cost of one cold cache-cold build across runner options at published per-minute rates. · Source: GitHub Actions pricing + Latchkey rates
Recommendations
Persist the layer cache close to the runner
The cache miss is the structural default of a cold hosted runner, and it is the root cause of most container-build slowness. Run builds where the layer cache survives between jobs and lives on local disk, so the build restores unchanged base-image and dependency layers at disk speed instead of rebuilding them. This is the single highest-return change and it requires no Dockerfile rewrite.
Enable BuildKit, then give it a warm place to cache
BuildKit is the right baseline for parallel stages, mount caches, and cache export, but its cache export is only as fast as the store it exports to. Pair BuildKit with a warm, local cache rather than a slow remote store, so the build skips the network transport and restores layers directly. BuildKit without a warm cache delivers modest gains; BuildKit with one delivers the large multiples.
Order Dockerfile instructions to protect the expensive layer
Copy manifest files and install dependencies before copying the full source tree, so a code-only change does not invalidate the dependency-install layer. This two-line reordering keeps the most expensive layer cached across the common case and costs nothing. Combined with a persistent cache, it ensures the expensive layers are both cacheable and actually cached.
Shrink the base image to cut pull, push, and scan cost
Image size is a recurring, fleet-wide tax paid on every pull, and it also drives scan time and the volume of scan findings. Move from a full distro base to a slim, distroless, or static base to cut compressed size by half or more, which shrinks registry-pull minutes, push time, and scan cost together. The decision is once; the saving recurs on every build and deploy.
Move the container build leg onto managed runners
Container-heavy pipelines concentrate their cost in repeated, cacheable work, which is exactly what a warm managed cache removes. A managed runner with a persistent layer cache cuts both the minutes the build burns and the per-minute rate it burns them at, and the two effects multiply. The more your pipeline is a build rather than a test suite, the larger the return.
Outlook
Expect the container build to remain the slowest step in the pipeline and the layer cache to remain the largest lever on its cost. As applications keep absorbing dependencies, multi-stage builds, and mandatory scan layers, the cold build only gets longer, which raises the value of keeping the cache warm. The separation between teams that persist a warm layer cache and teams that rebuild cold on every run will widen, because the cost of a cold build grows with everything the build accumulates.
Image-size discipline and supply-chain requirements will keep pulling in the same direction. Slim and distroless bases cut pull cost, scan time, and attack surface simultaneously, and as scan-gated merges become standard the teams on lean images will ship faster while teams on full distro bases pay a compounding tax on build, pull, and scan. The hygiene that started as a cost optimization is becoming a security and velocity requirement at the same time.
The durable takeaway is that container-build performance is a runner problem dressed up as a Dockerfile problem. The slow build and the fast build run the same image; the difference is whether the runner kept a warm cache the build could restore. Teams that treat the persistent layer cache as basic infrastructure, the way they already treat BuildKit, will spend the next two years building and shipping images in a fraction of the time and cost that cold-runner teams keep paying on every merge.
Methodology
This report combines publicly available container and CI pricing data with Latchkey's own analysis of container-build runner economics. Figures labeled "modeled" are illustrative estimates derived from published pricing and typical multi-stage build shapes, not a primary survey. Build-time multiples reflect representative pipelines and will vary with image, dependency graph, and cache locality. Pricing reflects published rates at time of writing and should be verified against current provider pricing.
Sources
- Docker - official documentation
- GitHub Actions - billing & pricing
- GitHub - Octoverse
- Stack Overflow Developer Survey