Skip to content

Fixing a Docker Build Context That Is Too Large for a Model Repo

9 min read · updated August 11, 2026

There is no error message that literally says the build context is too large. What you see is a build that appears to hang before running anything, on a line about transferring context — and on a repository containing checkpoints, that line is moving many gigabytes that no instruction will ever read.

What you are actually seeing

Docker’s documentation defines the build context as the set of files your build can access. The client packages that set and sends it to the builder before the first instruction runs, because the builder may be a different process or a different machine and cannot reach your working directory.

With BuildKit the symptom is a line reading => transferring context: 14.20GB that sits there for minutes. On the legacy builder it is Sending build context to Docker daemon followed by a size that keeps climbing. Neither is an error, which is why people wait rather than investigate.

The related failures are more alarming and have the same cause. The context is staged on the builder’s filesystem, so a large enough one produces no space left on device from a build whose image would have been small. And every byte of it is a byte the layer cache may key on, so a repository full of large mutable files also gives you a cache that misses constantly.

Find out what is in the context

Do not guess. Rank the directories under the context root by size and the answer is usually one line:

du -sh -- * .[!.]* 2>/dev/null | sort -h | tail -15

On a model repository the list is predictable: a .git directory whose history contains checkpoints committed before anyone set up LFS, a models/ or checkpoints/ directory, a data/ directory of evaluation sets, a local virtualenv, a wandb/ or mlruns/ directory of experiment artefacts, and the Hugging Face cache if anyone ever set HF_HOME to a path inside the repository.

To confirm what the builder actually receives after your ignore rules are applied, build a throwaway image that copies everything and list it. This tests the real rules rather than your reading of them:

printf 'FROM busybox\nCOPY . /ctx\n' > /tmp/ctx.Dockerfile
docker build -f /tmp/ctx.Dockerfile -t ctx-probe .
docker run --rm ctx-probe du -sh /ctx

The number that comes back is the context as the builder sees it. If it is still enormous after you have written ignore rules, the rules are not matching — and this tells you so in one step instead of three builds.

Write the .dockerignore

The file goes in the root of the build context — the directory you pass to docker build, which is not necessarily the directory the Dockerfile is in. This trips people up in monorepos: a .dockerignore next to a Dockerfile in a subdirectory is ignored when the build is invoked from the repository root.

# version control history, frequently the largest single item
.git
.gitignore

# weights, datasets and experiment output
models/
checkpoints/
data/
*.safetensors
*.ckpt
*.pt
*.bin
*.onnx
wandb/
mlruns/

# local environments and caches
.venv/
venv/
**/__pycache__/
.pytest_cache/
.mypy_cache/
.cache/

# things that are never needed in an image
notebooks/
docs/
*.md
!README.md

Docker documents the syntax as newline-separated patterns similar to Unix shell file globs, with # in column one starting a comment and leading and trailing slashes disregarded. Two rules are worth knowing exactly. ** matches any number of directories including zero, so **/*.pt excludes checkpoint files anywhere in the tree rather than only at the root. And a ! prefix re-includes: the last matching line for a given path wins, which is what makes the *.md / !README.md pair above behave as written — reverse those two lines and README is excluded again.

Excluding a path from the context means COPY cannot see it either. If a build genuinely needs a file you have ignored, the build fails with a “file not found” from the COPY, not from the ignore file, and the connection is not obvious at four in the afternoon.
Docker documentation on the build context

Per-Dockerfile ignore files

A repository that builds several images from one context usually needs different exclusions per image — the training image needs data/, the serving image must not have it. Docker supports a Dockerfile-specific ignore file named by prefixing the Dockerfile’s name: serve.Dockerfile.dockerignore applies to a build using -f serve.Dockerfile, and takes precedence over the root .dockerignore.

Precedence, not merger. If the specific file exists it is used instead of the root one, so it must repeat everything the root file excluded. Forgetting that is how .git ends up back in one image’s context after somebody adds a per-file ignore for an unrelated reason.

When ignoring is not enough

  • Point the build at a smaller directory. The context is an argument, and it does not have to be .. Building with docker build -f docker/serve.Dockerfile ./service sends only ./service, which is a stronger guarantee than any pattern list because there is nothing else to leak in.
  • Use a remote context. Passing a Git URL as the context makes the builder clone the repository itself, so nothing is uploaded from your machine at all. Combined with a shallow clone this is often faster than transferring a local checkout, and it side-steps the .git problem entirely.
  • Use named contexts for the exceptions. --build-context name=path attaches an additional named context that a COPY --from=name can read. It lets the main context stay tiny while one instruction still reaches a specific large directory, rather than widening the default context for everything.
  • Fix the repository. If the size is history rather than working files — checkpoints committed directly rather than through LFS — no ignore rule shrinks the clone every developer and every CI job pays for. That is a repository problem the build is merely reporting.
  • Reconsider what belongs in the image. If the reason you want the checkpoints in the context is to COPY them in, read baking versus mounting weights before optimising the transfer of something that may not need to be transferred.