skip to content

When you run `docker build -t app .`, what does the trailing `.` mean, and what actually gets sent to the builder before the first instruction executes?

level: juniorimportance: must knowfreq 58%

answer

  1. last argument = build context, not a flag
  2. CLI tars + ships it before step 1
  3. COPY is context-relative; ../ is forbidden
  4. transferring context: NNN MB
  5. context ≠ working directory for RUN

basics

~20 s

The . is the build context: the directory tree the CLI packages up and hands to the builder before building. Only files inside it can be used by COPY, and everything not excluded by .dockerignore is transferred.

solid answer

~50 s

The final argument to `docker build` is the **build context** — a directory the CLI walks, filters through `.dockerignore`, and transfers to the build daemon. `.` simply means 'the current directory'. Two consequences follow: 1. **COPY and ADD sources are relative to the context root**, and cannot escape it. `COPY ../shared /app` fails with a 'forbidden path outside the build context' error, because the builder only ever sees what was sent. 2. **Everything in that directory is candidate payload.** If the tree contains `.git`, `node_modules` and a `target/` directory, the build starts by shipping hundreds of megabytes that no instruction will use — visible as a long pause and, with BuildKit, a `transferring context` line. The fix is a `.dockerignore` file at the context root listing what to exclude. The context is not the environment the build runs in — `RUN` executes inside image layers, not in the context directory; the context is purely the set of files available to COPY.

code

bash · 3 lines
bash
du -sh .
du -ah . | sort -rh | head -20
docker build --progress=plain -t app .   # look for: transferring context

go deeper

for a junior

Say that the final argument is the directory sent to the builder, that COPY reads from it, and that .dockerignore keeps it small.

for a middle

Explain the transfer step and its output, why COPY cannot escape the context, and how context contents drive cache invalidation with COPY . ..

for a senior

Connect it to operations: remote builders and CI transfer cost, secret leakage through unfiltered contexts, and measuring context size as a routine check.

for a principal

Treat context definition as repository-layout policy — which directory is the context for which service, and how that choice bounds build time, cache hit rate and blast radius.

## The argument everyone types and few think about `docker build [options] PATH | URL | -`. That last positional argument is the **build context**, and it is the only channel through which host files reach the build. Understanding it explains a large share of everyday Docker friction: slow builds, cache misses, 'file not found' in COPY, and accidentally shipped secrets. ## What happens mechanically When you run `docker build -t app .`: 1. The CLI resolves `.` to a directory and looks for `.dockerignore` at its root. 2. It walks the tree, applying the ignore patterns. 3. The surviving files are transferred to the builder — historically a tar stream to the Docker daemon, printed as `Sending build context to Docker daemon 245MB`. With BuildKit (the default in modern Docker) the transfer is session-based and incremental: it shows `[internal] load build context` / `transferring context: 245MB`, reuses previously-sent content, and can send lazily. The walk and the filtering still happen on every build. 4. Only then does the Dockerfile execute, with `COPY`/`ADD` reading from that transferred snapshot. The builder may not be on your machine at all — a remote daemon, a Docker Desktop VM, a CI builder. That is precisely why files must be shipped rather than read in place, and why an oversized context is a real cost rather than a theoretical one. ## Rules that follow directly **Sources are context-relative and cannot escape.** `COPY package.json /app/` means 'the `package.json` at the context root'. `COPY ../lib /lib` is rejected. If a build needs files from a parent directory, the context must be that parent — typically `docker build -f services/api/Dockerfile .` from the repository root. **Absolute host paths do not work.** `COPY /etc/ssl/certs /certs` refers to the *context* root, not your machine's filesystem root. **The Dockerfile itself is separate.** By default the CLI looks for `Dockerfile` at the context root, but `-f` can point anywhere, including outside the context; the file is read by the client and does not need to be part of the payload. **The context is not a working directory.** `RUN ls` lists the filesystem of the image layer being built, not your host directory. Files only exist inside the build after a COPY or ADD puts them there. ## Symptoms of an unmanaged context - A pause of seconds to minutes before the first build step, with a large `transferring context` figure. - Cache misses on every build: with `COPY . .`, changing *any* file in the context — including an editor swap file or a fresh `.git` object — changes the layer hash and invalidates everything after it. - Bloated images when junk that was sent also gets copied in. - Leaked secrets: `.env`, `.git` history, `~/.aws`-style credentials or private keys that happen to sit in the directory become part of the payload and, with `COPY . .`, part of the image. - Out-of-space or timeout errors in CI, where the context is transferred over a network to a remote builder. ## `.dockerignore` A `.dockerignore` file at the context root lists patterns to exclude before transfer. It is the single highest-value file in most repositories that build containers: it typically cuts context size by an order of magnitude and stabilises the build cache. A serviceable starting point for most projects excludes version-control metadata, dependency directories, build outputs, local environment files and editor state: ``` .git node_modules dist build target *.log .env* .DS_Store ``` ## Other context sources The positional argument also accepts a Git repository URL (the builder clones it and uses that as the context), a URL to a tarball, or `-` to read a tar stream or a bare Dockerfile from stdin. `docker build - < Dockerfile` builds with an **empty** context, which is a useful way to prove a Dockerfile needs no local files. ## The habit to form Before adding a `.dockerignore`, look at what you are actually shipping: `du -sh .` for the total and `du -ah . | sort -rh | head -20` for the biggest offenders. Then check the builder's own `transferring context` number after the change. Treating context size as a number you watch — rather than something you discover during an incident — is what separates a tidy build from a five-minute one.

  • Why does `COPY ../shared /app/shared` fail even though the directory clearly exists on your machine?
    The builder never sees your machine's filesystem — it only receives the context that was sent, and paths are resolved inside it. Anything above the context root was never transferred, so the reference is rejected. The fix is to widen the context (build from the parent directory with `-f` pointing at the Dockerfile) rather than to use a relative escape.
  • How do you build with no context at all?
    Pipe the Dockerfile on stdin: `docker build - < Dockerfile`. The context is empty, so nothing is transferred and any COPY from the host will fail. It is a quick way to confirm a Dockerfile is self-contained, and it makes trivial builds fast.

The context is the box of materials you courier to a workshop. The workshop can only build from what is in the box — and you pay to ship every kilogram, used or not.

saying these in an interview costs you the question

  • Thinking the `.` is the location of the Dockerfile rather than the context directory.
  • Believing `RUN` commands execute in the host directory you built from.
  • Assuming COPY can reach files above the context with `../`.
  • Assuming BuildKit's incremental transfer makes context size irrelevant — the tree is still walked and filtered every build.
  • Not realising an unfiltered context can carry `.git` history or `.env` secrets into the image.

context