Run a build and watch the first line:
$ docker build .
[+] Building
=> transferring context: 189.74MB 2.0sThat happens before Docker reads a single instruction. The dot at the end of
docker build . is not decoration — it is an argument, and it names the directory that
gets packed up and handed to the builder.
That directory is the build context, and understanding it explains a build that
appears to hang doing nothing, a COPY that cannot find an obvious file, and one of the
easier ways to leak a credential.
Why it works this way
The builder is not your shell. On this machine it is inside a Linux VM; on a CI runner or a
remote builder it is another host entirely. It has no access to your filesystem, so anything
COPY might want has to be sent to it first.
Two rules follow, and they cover most build confusion:
Every COPY path is relative to the context, not to the Dockerfile.
Even when the Dockerfile lives somewhere else entirely.
COPY ../shared/lib . cannot work. Not "is discouraged" — cannot.
The parent directory was never sent. The error is
forbidden path outside the build context, and the fix is to build from a context high
enough to include everything you need:
docker build -f pizza-springboot-backend/Dockerfile pizza-springboot-backend
# └── which Dockerfile ──┘ └──── the CONTEXT ────┘-f and the context argument are independent. That is how a monorepo builds one
service with a Dockerfile stored next to it and a context that reaches shared code.
What it costs to get wrong
The React frontend, measured both ways on this machine:
$ du -sh node_modules
232M node_modules
# with .dockerignore
=> transferring context: 5.69kB
# with .dockerignore temporarily removed
=> transferring context: 189.74MB 2.0s189.74 MB versus 5.69 kB, for a build whose Dockerfile copies
package.json, package-lock.json and src/. Everything else was
sent for nothing.
Two seconds is survivable locally. On a CI runner pushing a context to a remote builder over a network it is not, and it happens on every single build.
It is a correctness problem too
This is the part that matters more than the speed.
node_modules holds binaries compiled for this machine — macOS,
arm64. The build runs npm ci inside Alpine Linux. If a stray COPY . .
drags the host's node_modules in, the build either fails with an error blaming esbuild,
or succeeds and produces an image that dies at start-up. Neither error mentions the copy.
Same shape for Java: target/ holds classes compiled by the JDK on your laptop.
Letting them into the context risks the build reusing them instead of compiling fresh — which is
precisely how "it works on my machine" becomes an image nobody can reproduce.
.dockerignore
Same syntax as .gitignore, sits at the root of the context, filters what gets
packed. Here is the React frontend's, in full:
node_modules/
dist/
test-results/
playwright-report/
screenshots/
e2e/
.env
.env.local
.git/
.gitignore
.idea/
.vscode/
README.md
Dockerfile
.dockerignoreFour groups, each for a different reason.
Big and wrong — node_modules/, dist/. The image builds
its own; a stale local copy could be used instead of the fresh one.
Big and irrelevant — Playwright output, screenshots. 2.3 MB of PNGs that no instruction copies.
Dangerous — .env.local holds a real Stripe key, and
.git/ holds every version of every file ever committed, including any secret that was
committed and later removed. ⚠️ Remember lesson 3: a file copied into a layer is in that layer
permanently. A later RUN rm hides it and ships it.
Cache-busting — README.md, the Dockerfile itself. Nothing copies
them, but if a COPY . . is ever added, editing the README would invalidate every layer
after it. Excluding them makes the cache depend only on things that matter.
Check what you are actually sending
You do not have to guess:
docker build --progress=plain --no-cache . 2>&1 | grep "transferring context"If that number is much larger than your source tree, something is being sent that should not be. It is a ten-second check and it is the first thing to run on a build that feels slow.
Next: why the second build is instant, and how to keep it that way.