✦ For everyone, free.

Practical knowledge for real and everyday life

Home

16.1.1 Build Context Issues

A focused guide to Build Context Issues, connecting core concepts with practical Docker and container operations.

Build context issues are problems stemming from what is actually sent to the Docker daemon (or BuildKit) at the start of a build, the directory tree specified as the build context, distinct from problems in the Dockerfile's instructions themselves, and they manifest as slow builds, unexpectedly large images, missing files during a COPY, or files appearing in the image that should never have been included at all.

What the build context actually is

Before any Dockerfile instruction executes, Docker packages the entire directory specified as the build context (commonly ., the current directory) and sends it to the daemon, which means every file in that directory tree is read and transmitted regardless of whether the Dockerfile ever actually references it:

docker build -t my-api .
Sending build context to Docker daemon  847.3MB

This packaging step happens before any .dockerignore-unaware logic could otherwise skip irrelevant files, which is exactly why .dockerignore exists: without it, every file in the directory, including ones the Dockerfile never touches, is included in what gets sent.

Diagnosing an unexpectedly large context

A build context far larger than the application's actual source code usually indicates unwanted directories, build artifacts, dependency directories, version control metadata, are being included unnecessarily:

du -sh .
du -sh */ | sort -rh | head -10

Running a directory size breakdown directly against the intended build context root quickly identifies which specific subdirectories are contributing disproportionately to its size, which is usually the fastest way to identify what a .dockerignore entry should be excluding.

Writing an effective .dockerignore

A .dockerignore file, placed in the build context root, excludes matching files and directories from being sent to the daemon at all, using syntax similar to .gitignore:

node_modules
.git
*.log
dist
.env
**/*.test.js

Excluding directories like node_modules is particularly valuable not just for build speed but for correctness: a node_modules directory built on the host machine, with potentially different architecture-specific binaries than the container's target environment, should generally never be copied into the image directly, relying instead on the Dockerfile's own RUN npm install to produce a version built correctly for the container's actual environment.

Files referenced by COPY but outside the context

A Dockerfile can only reference files within its build context; attempting to reference a file outside that directory tree, even using a relative path that appears to point outside it, fails because the daemon never received that file as part of the context in the first place:

COPY ../shared-lib/utils.js ./utils.js
COPY failed: forbidden path outside the build context

The fix is either restructuring the project so the needed file is within the build context, or running the build from a higher-level directory that encompasses everything needed, with the Dockerfile's path specified explicitly:

docker build -f app/Dockerfile -t my-api .

Dockerfile location versus build context location

The build context and the Dockerfile's location are two independent things, and confusing them is a common source of COPY instructions failing unexpectedly: the context is whatever directory is passed as the final argument to docker build, while -f specifies where the Dockerfile itself is, which does not need to be inside that context directory at all:

docker build -f docker/Dockerfile.prod -t my-api .

In this example, the Dockerfile lives in a docker/ subdirectory, but the build context is still the repository root (.), meaning every COPY instruction inside that Dockerfile resolves paths relative to the repository root, not relative to the Dockerfile's own location.

Remote build contexts

Docker also supports specifying a remote Git repository directly as the build context, which bypasses the local .dockerignore and context-size concerns entirely, since the daemon clones the repository itself rather than receiving a packaged local directory:

docker build https://github.com/example/my-api.git#main

This is useful for CI or automation scenarios building directly from a repository without first checking it out locally, though it depends on network access from wherever the build is actually executing to the remote repository.

Build context with BuildKit's multiple contexts

Newer BuildKit-based builds support specifying multiple named contexts, useful for referencing files or even entire other images as additional sources within a single build, beyond the single, traditional build context directory:

docker buildx build --build-context shared=../shared-lib -t my-api .
COPY --from=shared utils.js ./utils.js

This pattern is a cleaner alternative to restructuring an entire project layout just to bring a file within a single traditional build context, allowing a genuinely separate directory to be referenced explicitly and intentionally as its own named context.

Context transfer time on remote or resource-constrained daemons

When building against a remote Docker daemon, or one running with constrained resources, the time spent transferring a large build context can dominate overall build time even before any actual build instructions begin executing:

time docker build -t my-api .
Sending build context to Docker daemon  1.1GB
[+] Building 45.2s

A build where context transfer alone takes a meaningful fraction of total build time is a strong, measurable signal that context size reduction, through a more thorough .dockerignore, would yield a disproportionate improvement relative to the effort required to add it.

Common mistakes

  • Missing or incomplete .dockerignore entries, sending unnecessary directories like node_modules, .git, or build artifacts to the daemon on every build.
  • Copying a host-built node_modules or similarly platform-specific dependency directory directly into the image instead of letting the Dockerfile's own install step produce a version correct for the container's environment.
  • Attempting to reference a file outside the build context with a relative path, rather than restructuring the project or adjusting which directory is passed as the context.
  • Confusing the Dockerfile's location with the build context's location, leading to unexpected COPY path resolution failures.
  • Not measuring context transfer time specifically when a build feels unexpectedly slow, missing a context-size problem that a more thorough .dockerignore would directly resolve.

Build context issues are largely preventable through a deliberate, maintained .dockerignore file and a clear understanding that the context is a snapshot taken before any Dockerfile instruction runs, packaged and transferred in full regardless of which files the Dockerfile actually ends up using, and many confusing COPY failures resolve immediately once the actual context boundary and its independence from the Dockerfile's own location are understood correctly.

Content in this section