Skip to main content

What is .dockerignore and why do you need it?

.dockerignore is the simplest Docker file you will ever write. It is also one of the most impactful: it controls what gets sent to the daemon during docker build and prevents three classes of bugs (slow builds, bloated images, leaked secrets).

Theory

TL;DR

  • Build context = the directory you give to docker build. Everything in it gets uploaded to the daemon before the build starts.
  • Without .dockerignore, the WHOLE directory is sent — node_modules, .git, build outputs, IDE config, secrets, the lot.
  • .dockerignore syntax is gitignore-like with one exception: only one ** glob pattern type, no negation in some Docker versions.
  • Should be at the build context root (next to Dockerfile).
  • Three reasons it matters: build speed, image size, security.

Quick example

# .dockerignore node_modules .git dist build *.log *.tmp .env* .DS_Store Dockerfile* .dockerignore README.md .vscode .idea coverage tests *.test.js *.spec.ts

With this, docker build . only sends actual source files. Without it, a typical Node project sends 200+ MB of node_modules and a .git folder full of history that does not need to be in the image.

Pattern syntax

# Comment *.log # any .log file at any depth logs/ # the logs directory and everything inside it !important.log # negation: include even if matched above **/temp # any 'temp' directory at any depth src/**/*.test.js # test files anywhere under src/

Important: order matters. Later rules override earlier ones. Use ! for exceptions.

Why each entry matters

node_modules (and equivalents)

The single biggest contributor. Often hundreds of MBs. The Dockerfile reinstalls them via npm ci anyway.

node_modules vendor # PHP / Go / Ruby vendored deps .venv # Python virtualenv __pycache__ # Python bytecode cache *.pyc

.git

The full git history. Often 50-500 MB on long-lived repos. Almost never needed in an image.

Exception: if your build needs git info (commit SHA via git rev-parse), use git rev-parse HEAD > VERSION BEFORE build, then exclude .git and COPY VERSION only.

Build outputs

dist build out target # Rust / Java

You rebuild these inside the image. No reason to ship the host's outputs in.

Secrets and local config

.env* *.pem *.key secrets.yaml config.local.json

A .env accidentally COPYed into the image is a leaked production secret.

IDE / editor / OS clutter

.vscode .idea .DS_Store # macOS Thumbs.db # Windows *.swp # vim

Tests and dev artifacts (sometimes)

coverage tests # if you do not run tests inside the image *.test.js

Depends on your build flow. If you RUN npm test inside the build (multi-stage), keep tests. Otherwise skip them.

Common mistakes

No .dockerignore at all

$ docker build -t myapp . [+] Building 0.0s => transferring context: 248.7MB ← too big

248 MB upload to the daemon every build. Add .dockerignore and watch this drop to a few MB.

Forgetting that COPY . . copies whatever is in context

dockerfile
COPY . . # copies everything — including .env if no .dockerignore

If .env exists in your build context AND it is not in .dockerignore, it lands in the image. Auditors love finding these.

Putting .dockerignore in a subdirectory

Docker only reads .dockerignore from the build context root. The build context is what you pass to docker build (typically . = current directory). A .dockerignore in ./api/.dockerignore is ignored unless you build with docker build ./api.

Negating patterns and being surprised

* !Dockerfile !src/**

This allow-listy pattern is fragile and order-dependent. Easier to use deny-list rules (node_modules, .git, dist).

Forgetting the file affects multi-stage builds too

The build context is determined once for docker build, regardless of how many stages your Dockerfile has. Every stage sees the same filtered context.

Inspecting what is in the build context

bash
# See what would be uploaded docker build --no-cache --progress=plain -t test . 2>&1 | head -10 # Look for the "transferring context" line # Or do a dry run with a manual tar tar --exclude-from=.dockerignore -cf - . | wc -c

If you have ever wondered "why is my build context 1 GB?" — du -sh */ .[^.]*/ 2>/dev/null | sort -h from the build root tells you what is biggest.

Real-world usage

  • Every production project: has a .dockerignore. The default starter for any new repo is to add it before the Dockerfile.
  • Monorepos: more important — node_modules directories per package can total gigabytes.
  • CI builds: large build context multiplied by every CI run = real disk and time costs.
  • Cloud builds (Cloud Build, GitHub Actions): they upload the context to a remote service. Smaller is faster (and cheaper).

Follow-up questions

Q: What happens if I COPY a file that is excluded by .dockerignore?


A: Docker errors out with "file not found" — from Docker's perspective, the file is not in the context.

Q: How is .dockerignore different from .gitignore?


A: Same syntax (mostly). .gitignore controls what git tracks. .dockerignore controls what docker build sends to the daemon. Independent files, often overlapping but not identical (e.g., .git itself is not ignored by .gitignore but should be by .dockerignore).

Q: Does .dockerignore affect docker run -v mounts?


A: No. .dockerignore is build-time only. Bind mounts at run time mount whatever you point at, regardless of .dockerignore.

Q: Can .dockerignore be in a different location?


A: Yes, with BuildKit you can use # syntax=docker/dockerfile:1.7 and a <Dockerfile>.dockerignore file. Useful when one repo has multiple Dockerfiles with different ignore needs. The default is still .dockerignore at the build context root.

Q: (Senior) Why might a tightly-locked-down .dockerignore slow down dev workflow?


A: If it excludes things that some Dockerfile expects to find (test fixtures, dev configs), COPY will fail or the image will misbehave. Common pitfall: excluding .git while a build step needs git log for a version. Solutions: pre-compute the value (git rev-parse HEAD > VERSION before build), pass via --build-arg, or use a per-Dockerfile .dockerignore variant for dev vs prod.

Examples

A typical Node project's .dockerignore

# Dependencies (rebuilt inside image) node_modules bower_components # Build outputs (rebuilt inside image) dist build out # Logs and temp *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Environment / secrets .env .env.* !.env.example # keep example for documentation # Editors .vscode .idea *.swp .DS_Store # Tests / coverage coverage *.test.js *.spec.ts # Docker Dockerfile* .dockerignore compose*.yaml # VCS .git .gitignore .gitattributes

Result: a docker build context that contains exactly the source files needed. Build time drops dramatically; the image is smaller; no secrets leak.

Before vs after measurement

bash
# Before .dockerignore $ time docker build -t myapp . [+] Building ... transferring context: 312.4MB real 1m45s # Add .dockerignore with node_modules and .git $ time docker build -t myapp . [+] Building ... transferring context: 4.2MB real 0m22s

Two lines in .dockerignore, 5x faster build. The numbers compound across CI runs.

Multi-Dockerfile project (BuildKit)

repo/ ├── api/ │ ├── Dockerfile │ └── Dockerfile.dockerignore # specific to api ├── web/ │ ├── Dockerfile │ └── Dockerfile.dockerignore # specific to web └── .dockerignore # shared baseline

With # syntax=docker/dockerfile:1.7, BuildKit auto-loads <Dockerfile>.dockerignore if present, otherwise the root .dockerignore. Useful when api and web have different exclusion needs.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Comments

No comments yet