MervCodes

Tech Reviews From A Programmer

How to Fix Docker Build Context Too Large

1 min read

If you have ever run docker build and watched it hang for several seconds — or minutes — on the line Sending build context to Docker daemon, you have met the "build context too large" problem. A bloated build context slows every build, wastes disk and network, and can even cause builds to fail outright in CI environments with strict size limits.

This guide explains what the build context actually is, why it gets too large, and the concrete steps you can take to shrink it back down.

What Is the Docker Build Context?

When you run a command like docker build ., that trailing . is the build context: the directory whose contents Docker packages up and sends to the Docker daemon before the build begins. Every COPY and ADD instruction in your Dockerfile can only reference files that live inside this context.

The important detail many people miss is that Docker sends the entire directory tree — not just the files you actually COPY. If your project folder contains a 2 GB node_modules, a .git history, build artifacts, local databases, and log files, all of it gets bundled and transferred to the daemon even if your Dockerfile never touches a single one of those files.

That is why you see output like:

Sending build context to Docker daemon  1.847GB

A large number there is the symptom you are trying to fix.

Step 1: Measure the Context

Before fixing anything, find out what is making the context big. The fastest way is to check directory sizes:

du -sh * .[^.]* | sort -h

Run this from the same directory you pass to docker build. The biggest offenders are usually predictable:

  • node_modules/ (JavaScript projects)
  • .git/ (long commit histories with large binaries)
  • vendor/ (Go, PHP)
  • target/ (Rust, Java/Maven)
  • dist/, build/, .next/, __pycache__/
  • Local data: *.sqlite, *.log, data/, tmp/

Once you know what is heavy, you can decide what to exclude.

Step 2: Add a .dockerignore File

The single most effective fix is a .dockerignore file. It works just like .gitignore: any path it matches is excluded from the build context before anything is sent to the daemon.

Create a file named .dockerignore in the same directory as your Dockerfile:

# Version control
.git
.gitignore

# Dependencies (re-installed inside the image)
node_modules
vendor
target

# Build artifacts
dist
build
.next
*.pyc
__pycache__

# Local environment and secrets
.env
.env.*
*.local

# Logs and temp files
*.log
tmp
*.sqlite

# Editor and OS noise
.vscode
.idea
.DS_Store

# Docker files themselves
Dockerfile
docker-compose.yml

A few notes on behavior:

  • Patterns are matched relative to the context root.
  • A leading ** matches across directories, e.g. **/*.log.
  • You can re-include a file with !, e.g. exclude everything then add back what you need.

Excluding node_modules is almost always correct: you should be running npm install or npm ci inside the Dockerfile so dependencies are built for the container's platform, not copied from your host machine.

Step 3: Use an Allowlist Pattern for Maximum Control

For projects where you only need a handful of files, an allowlist is cleaner than a long denylist. Ignore everything, then explicitly un-ignore what you need:

# Ignore everything
*

# Then allow only what the build needs
!package.json
!package-lock.json
!src/
!tsconfig.json

This approach is robust because new junk files added to your repo won't accidentally leak into the context. The trade-off is that you must remember to whitelist any genuinely new source directory.

Step 4: Narrow the Context Directory Itself

You do not have to use . as your context. If your buildable code lives in a subfolder, point Docker there:

docker build -f ./docker/Dockerfile ./backend

Here ./backend is the context and -f points to a Dockerfile that lives elsewhere. By scoping the context to just ./backend, you exclude everything in sibling directories automatically. This is especially helpful in monorepos where a single repo holds many services.

Step 5: Use Multi-Stage Builds to Keep Images Lean

A large context and a large final image are related problems. Multi-stage builds let you pull in heavy tooling in an early stage and copy only the finished artifacts into a slim final stage:

# Build stage
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Runtime stage
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Notice the ordering: copying package*.json and running npm ci before copying the rest of the source lets Docker cache the dependency layer. Combined with a good .dockerignore, this keeps both the context and the final image small.

Step 6: Use BuildKit and Remote Contexts

Modern Docker uses BuildKit by default, which is smarter about transferring context — it can skip unchanged files incrementally. Make sure it is enabled:

export DOCKER_BUILDKIT=1
docker build .

For very large or remote-stored codebases, you can also build directly from a Git URL, which sends only the repository rather than your entire working tree including untracked artifacts:

docker build https://github.com/dockersamples/example-voting-app.git#main

You can even pipe a Dockerfile through stdin with no context at all when the build doesn't need local files:

docker build - < Dockerfile

Step 7: Verify the Fix

After adding your .dockerignore, rebuild and watch the context size:

docker build . 2>&1 | grep -i "build context"

With BuildKit you can also inspect exactly what is being sent by adding a temporary stage that lists files, or by checking the transfer step in the build output. The number should drop dramatically — often from gigabytes to a few megabytes.

Common Pitfalls

  • .dockerignore in the wrong place. It must sit at the root of the build context, not next to the Dockerfile if those are different directories.
  • Forgetting .git. A long history with committed binaries can be hundreds of megabytes on its own.
  • Copying host node_modules. Besides bloating the context, native modules built for your OS may not run inside the container.
  • COPY . . with no ignore file. This is the classic cause — it pulls in everything.
  • Symlinks pointing outside the context. Docker cannot follow them, and large linked targets can still confuse expectations.

FAQ

Why does Docker send the whole folder instead of just the files I COPY?

Docker evaluates COPY and ADD instructions on the daemon side, which may be on a different machine entirely. It cannot know in advance which files you will reference, so it transfers the whole context first, then resolves instructions against it. This is exactly why .dockerignore matters.

Does .dockerignore reduce my final image size?

Indirectly. Its primary job is shrinking the context that gets sent to the daemon, which speeds up builds. But by preventing junk from being available to COPY . ., it also stops that junk from ending up in your image layers — so it usually reduces image size too.

What's the difference between .dockerignore and .gitignore?

They use similar pattern syntax but serve different tools. .gitignore controls what Git tracks; .dockerignore controls what Docker includes in the build context. A file can be tracked by Git but ignored by Docker, or vice versa. Keep them separate even if their contents overlap.

How big is "too large"?

There is no hard limit, but anything over a few hundred megabytes is worth investigating, and CI systems may impose their own caps. A well-tuned context for most applications is single-digit to low-tens of megabytes — essentially just your source code and manifest files.

Will enabling BuildKit alone fix the problem?

BuildKit helps by transferring context incrementally and caching aggressively, so subsequent builds feel faster. But it still respects .dockerignore and still needs to know your context boundary. Use BuildKit and a good ignore file together for the best result.

Can I have multiple .dockerignore files?

Standard Docker uses one .dockerignore at the context root. However, BuildKit supports per-Dockerfile ignore files named <Dockerfile>.dockerignore, which is useful when one repo has several Dockerfiles with different exclusion needs.

Conclusion

A "build context too large" warning is almost always a sign that Docker is shipping files it doesn't need. The fix is rarely complicated: measure what's heavy, add a precise .dockerignore, scope your context directory, and lean on multi-stage builds and BuildKit. Do these once and your builds will be faster, your images leaner, and your CI pipelines noticeably happier.

Sources

Related Articles