Image builds hit minutes: where the bottleneck is
Configuring Docker BuildKit starts with an honest answer to the question of why rebuilding a project takes minutes even though only one file changed in the code.
The cause is almost always the same: layer reuse is not working — the cache is reset at the very first instruction, and all subsequent steps run again.
On top of that comes sequential stage processing: until dependency installation completes, compilation does not even start, even though these steps are independent of each other.
Long image builds are perceived as normal right up until you count how much time is spent on this per week.
The second source is the size of the build context. If logs, local artifacts, dependency directories, and temporary files get into the directory, every run sends them to the build service again, and time goes into transfer rather than work.
Finally, local and server builds diverge: on the developer's machine the cache is warm, in the build environment it is empty, and the same image builds in seconds locally and in minutes on the server.
This discrepancy masks the build bottleneck — it is sought in the code, while it lies in the configuration and order of steps.
The cost is predictable: waiting in line for handoff, infrequent releases, the habit of "building less often and for everyone at once," and rolling back to a previous version turns into a separate operation with its own risks. Below we break down how this is addressed.
Layer reuse and parallel steps: what this gives
Reducing rebuild time
A rebuild after editing one line stops rebuilding the entire image: layers with dependencies are reused, and only the changed step is rebuilt. Parallel steps — package installation, linters, compilation — run simultaneously rather than queuing up.
On a typical service, a rebuild is reduced from tens of minutes to one or two. According to public measurements by Docker BuildKit, the share of reused layers on repeated runs exceeds 80%.
The more often you deploy, the stronger the effect: waiting for the build stops holding back releases.
Fewer discrepancies between local and server builds
Instructions run in the same environment on the engineer's machine and on the build node: tool versions, environment variables, and mounted caches are fixed in the configuration rather than in agreements between people. The error "builds locally, fails on the server" moves from regular to rare.
A unified build process produces a reproducible artifact: an image verified on staging reaches production the same way. Deployment stability grows due to repeatability, not due to manual checks before every release — and this sustainably speeds up the entire deployment chain.
Docker BuildKit setup: build modes and what to choose
Choosing a build mode is not "enable BuildKit" but deciding where state lives: layers, intermediate artifacts, and dependencies.
The same image builds in minutes or in tens of minutes — the difference is how well a rebuild lands in already prepared layers and how the pipeline behaves when the cache is unavailable.
Below are four modes that are actually chosen between, and their limitations.
| Option | When it fits | Limitations |
|---|---|---|
| Regular build | One-off image on one branch, no requirements for build time | Every run rebuilds layers from scratch; rebuilds take as long as the first build |
| Layer reuse between runs | Frequent releases of one branch, builds on one executor or with a shared disk | Cache is tied to the machine: moving to another executor means warming from scratch; storage grows without cleanup |
| Shared cache between branches and executors | Multiple teams, parallel builds, recurring dependencies | Requires external storage and an eviction policy; higher requirements for access separation |
| Build on a separate machine | Different architectures, heavy builds, isolation of build node resources | The node must be updated and monitored; context is transferred over the network, adding overhead |
Limitations of a shared cache between branches
A layer key is calculated from the content of the instruction and input files, so branches with diverging steps do not share layers — the benefit goes primarily to those who frequently build close revisions.
Then hygiene begins: without an eviction policy, storage grows faster than build time shrinks, and secrets that get into a layer are distributed along with the cache to everyone who has access to it.
We separately check degradation — when storage is unavailable, the build must complete in regular mode rather than fail.
Step-by-step setup: from build audit to handover of the procedure
We start with measurement, not with editing configuration: accelerated build without an audit gives gains on a clean repository and breaks the cache on real changes. The order is the same for a single service and for a monorepo with a dozen images.
- Build audit. We capture baseline values: full and incremental build time, image size, share of steps that hit the cache, and what most often resets it — copy order, dependency installation after code, absence of
.dockerignore. - Build scheme: layers and stages. We break the process into stages, define the common base, locations for mounting package manager caches, and layers that survive a rebuild unchanged.
- Setup of build executors and permissions. We decide where the daemon runs — on a dedicated node, in a container, or in rootless mode — and grant minimally sufficient access to the registry.
- Enabling the mode. We edit the daemon configuration and the build file, and configure cache export to the registry so it is reused not only on one machine.
Verifying the result on production repositories
- Verifying the build result. We run it on two or three real repositories, compare with baseline values by time and cache hits, and make sure the previous build method remains working for rollback.
- Handover of the procedure. We give the team a description of the scheme, a build file template, rules for working with the cache and updating base images, and a list of metrics for on-call duty.
Configuration files, schemes, and build procedures
The result of the work is not only a fast build but also a package of artifacts by which the team reproduces it without us. Everything lives in the repository next to the code, goes through review as a regular change, and is versioned together with the release.
- Build configuration file — enabled modes, parallelism limits, caching policy; each parameter has a comment explaining why it was chosen.
- Layer and step scheme — what goes into the common layer, what is rebuilt when code is edited, where cache boundaries are and why.
- Image repository access rules — roles, credential lifetime, who can publish, overwrite a tag, and delete images.
- Description of limitations — what the build deliberately does not do and which conditions break it, so the team does not expect too much.
- Release procedure — the sequence from commit to finished image, checkpoints, and stop conditions.
- Build recovery instructions — how to bring it up from scratch if the cache is lost, where base layers come from, how to roll back to the previous image.
- Acceptance checklist — a set of checks by which a configuration change is considered safe.
- Knowledge transfer map — a record of the walkthrough with the team and answers to questions that arose along the way.
What the team will be able to change independently
After that, edits are made without us: change parallelism limits, add a step, rearrange layer order, connect a new service to the storage.
The procedure states directly which changes are safe and which require rebuilding the base layer and therefore are done as a separate release.
If something goes wrong, the recovery instructions restore a working build in one pass. We remain available during support, but no point of no return is created: the team owns the configuration and understands its logic.
Case study: build time reduced severalfold, deployment failures eliminated
The client is a product team with a multi-service repository and a shared image base. A full build took 18 minutes, a rebuild after editing one service — 11: the developer waited for the result longer than they wrote code. Deployment failed several times a month due to discrepancies between local and build environments.
Measurements were taken on their staging environment from build logs, without synthetic scenarios: any edit in such a structure pulled in a rebuild of almost everything.
| Metric | Before | After |
|---|---|---|
| Full build | 18 min | 4 min |
| Rebuild after editing a service | 11 min | about 1 min |
| Share of reused layers | 20% | 85% |
| Deployment failures per month | 7 | 0 |
| Return to working state | 40 min | 3 min |
What was done within the build process
We moved the build to Docker BuildKit so intermediate layers persist between runs instead of being created anew. Stages were separated: dependencies, tests, artifact, final image — editing code affects only the latter ones.
We moved the cache to a shared volume on the build node with an explicit storage mode: it survives individual jobs and is available to parallel builds.
We pin base images by digest, and we verify the build configuration before deployment — the discrepancy between local and server builds no longer reaches the production environment.
There are no deployment failures left, and returning to a working state takes minutes: rollback reuses unchanged layers. Measurements were taken from build logs — the export and stage scheme were handed over to the client's team.
What to do if the build already works and cannot be broken?
Yes — the transition is done with a parallel scheme, not a "one-day" replacement.
BuildKit build mode is enabled separately at the daemon level and separately for a specific project, so the new pipeline and the existing one can work simultaneously: some services build as before until a new flag appears in their configuration.
The practice is this: alongside, we bring up a second build agent on the new scheme; it reads the same repositories and pushes to the same image registry.
Then we move one or two non-critical services to it and compare build time, image size, and cache hit rate — before and after. We expand the list of projects only after the numbers match.
Before enabling, everything is run on a copy: we deploy a duplicate registry, bring up a copy of the pipeline, and build current releases on it. This is cheaper than finding out in production that a multi-stage build with cache mounting conflicts with the remaining old agents.
Risks are predictable and known in advance. The new cache is not read by the previous one, so the first run rebuilds layers from scratch — for this we reserve headroom in time and load on agents.
Secrets that come through mounting must not settle in image layers: in Docker this is separated by mount types, and during migration we check against the official documentation on build modes.
Rollback is one command: the agent switches back to the previous mode, the old cache remains warm, and there will be no full rebuild. This is exactly why enabling is done in stages and without a downtime window.
Limitations, compatibility, and typical technical questions
Doubts before enabling a new builder usually concern not the setup itself but its consequences: resources, compatibility with old scenarios, access, and rollback. We address five questions that are asked before the start.
How much will the load on the build server increase?
The builder maintains a service process and a local cache, so peak disk and memory consumption on the executor is higher than with a regular build. On the other hand, rebuilds reuse layers and run faster, and cache size is limited by the storage policy and cleanup rules.
What are the requirements for build executors and servers?
Kernel-level isolation support is required, along with a separate account and permissions to start the service process. Executors are combined into a group with a shared cache — then heavy layers are built once rather than on every machine.
Will build scripts have to be rewritten?
No. Existing instructions remain working; the builder is enabled by a parameter or environment variable. Only the context copy order and multi-stage build are worth rewriting — that is where the main time savings appear.
What about data isolation and access?
Each build runs in its own namespace, secrets are mounted for the duration of a step and do not settle in image layers. The cache is shared, so only trusted branches are allowed into it — otherwise intermediate artifacts can be leaked through it.
How do we roll back if something goes wrong?
Rollback is switching the build method back, one setting in the executor configuration. Images in the registry are not overwritten, so the previous release remains available and deploys in minutes.
Let's discuss the task: send the build scheme and limitations
To come back with an estimate and work plan rather than general considerations, we need input data for assessment.
Docker BuildKit setup relies on how your pipeline is structured today: where the layer cache lives, what goes into the build context, which steps are rebuilt when one line is edited. Describe the task — we will put together the picture and propose an order of changes.
- Current build scheme: which images are built, on which agents, from which base layers, and what currently starts the build.
- Project composition: one repository or several, whether there are common base images and who depends on them.
- Bottlenecks: what exactly is slow — dependency installation, compilation, image export to the registry, rebuild when code is unchanged.
- Release timeline requirements: how many builds per day, how quickly a finished artifact is needed, whether speed on the development branch matters separately from the release branch.
- Access limitations: where build agents are allowed to go — internal registry, package mirrors, private dependencies.
- Infrastructure boundaries: own machines or managed agents, how much space can be allocated to the cache and where it must survive a restart.
- Invariants: security policies, artifact checks, requirements for build reproducibility, and the ability to roll back to the previous release.
With this set, we will state which changes will have an effect first and which can be postponed, and we will fix the scope of work by timeline. Describe the task in a convenient form — we will come back with an estimate and plan.







