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. .dockerignoresyntax 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.tsWith 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 / JavaYou 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.jsonA .env accidentally COPYed into the image is a leaked production secret.
IDE / editor / OS clutter
.vscode
.idea
.DS_Store # macOS
Thumbs.db # Windows
*.swp # vimTests and dev artifacts (sometimes)
coverage
tests # if you do not run tests inside the image
*.test.jsDepends 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 big248 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
COPY . . # copies everything — including .env if no .dockerignoreIf .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
# 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 -cIf 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_modulesdirectories 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
.gitattributesResult: 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
# 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 0m22sTwo 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 baselineWith # 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 readyA concise answer to help you respond confidently on this topic during an interview.
Comments
No comments yet