Use .dockerignore to reduce build context
Use .dockerignore to reduce build context in Docker. Learn how to exclude unnecessary files from the Docker build context, speeding up builds, reducing image size, and improving security — with a hands-on exercise.
Focus: use .dockerignore to reduce build context
You docker build a project, and every time you sneeze, the build context uploads your entire home directory — megabytes of .git, node_modules, and raw footage from last year's vacation. That's not just slow; it's a security liability and a waste of CI minutes. The fix is a single text file: .dockerignore. This lesson shows you exactly how to use .dockerignore to reduce build context, why it matters, and the traps to avoid.
The problem this lesson solves
Every docker build command sends a build context — the directory you specify at the end of the command — to the Docker daemon. If you run docker build . in a project that contains a 300 MB node_modules folder, Docker copies every file in that directory over the network to the daemon (even if your Dockerfile later deletes or ignores them).
The result is painful:
- Slow builds — uploading unused data takes seconds to minutes.
- Large images — the build context is cached, but bloated layers stick around.
- Security risks — secrets, .env files, or SSH keys in the context can end up in intermediate layers of the image.
- CI cost — repeated massive uploads burn bandwidth and runner time.
Without a .dockerignore, the Docker CLI and daemon have no way to know which files are irrelevant. You need a declarative filter — just like .gitignore — to tell Docker: “Don’t bother sending these.”
Core concept / mental model
Think of .dockerignore as a gatekeeper for the build context. When you run docker build, the Docker CLI reads .dockerignore from the build context root, applies the patterns to the filesystem, and sends only the files that match none of the ignore rules to the daemon.
Mental model:
.dockerignoreis todocker buildwhat.gitignoreis togit add. It prevents unwanted files from entering the staging area (build context).
The daemon never sees the ignored files. They don't appear in layers, can't be copied with COPY ., and don't bloat the cache. The result: a lean, fast, secure build pipeline.
How the Docker CLI processes ignore rules
- Read the file named
.dockerignorein the root of the build context (where you rundocker build). - If no
.dockerignoreexists, the entire context directory is sent (minus some built-in exclusions like.dockerignoreitself on newer clients). - The daemon receives only the filtered files.
- The Dockerfile then operates on that reduced set.
How it works step by step
- Create a
.dockerignorefile in the same directory as yourDockerfile. If your build context is/app, the file must be/app/.dockerignore. - Write exclusion patterns — one per line. Patterns use the standard Go
filepath.Matchsyntax, with a few Docker-specific twists like**for recursive matching. - Order matters: later patterns override earlier ones. You can use
!to re-include files that would otherwise be ignored. - Test it — use
docker build -f - . 2>/dev/null | grep 'context'or inspect the sent context size withdocker build --no-cache --progress=plain .and look for=> [internal] load build context— it shows the total transferred bytes.
Pattern syntax quick reference
| Pattern | Meaning |
|---|---|
node_modules/ |
Ignore any directory named node_modules at any level |
*.log |
Ignore all .log files in the root of the context |
**/.git |
Ignore .git directories recursively |
!important.log |
Re-include important.log even if *.log is ignored |
secret/*.pem |
Ignore all .pem files in a secret directory at root |
# comment |
Lines starting with # are comments |
| empty line | Blank lines are ignored |
Pro tip: Always include
.gitandnode_modulesif they exist in the context. Even if you don't copy them in the Dockerfile, they contribute to the context size and can leak repository metadata.
Hands-on walkthrough
Let's create a minimal Node.js project and see the before/after with .dockerignore.
Step 1: Create a sample project
mkdir /tmp/dockerignore-demo
cd /tmp/dockerignore-demo
# Create some junk to simulate real bloat
mkdir node_modules .git
for i in $(seq 1 10); do
dd if=/dev/urandom of=node_modules/module_$i.dat bs=1M count=5 2>/dev/null
done
for i in $(seq 1 5); do
dd if=/dev/urandom of=.git/obj_$i.dat bs=1M count=2 2>/dev/null
done
# Create a real file
echo 'console.log("hello")' > index.js
echo 'FROM node:18-alpine
COPY . /app
CMD ["node", "/app/index.js"]' > Dockerfile
Check the context size without .dockerignore:
docker build --no-cache --progress=plain . 2>&1 | grep -i context
Expected output (size will vary, but you'll see ~70 MB):
#12 [internal] load build context
#12 transferring context: 70.15MB 0.4s done
That's 70 MB of unnecessary data — the daemon received node_modules and .git even though the Dockerfile never uses them.
Step 2: Add .dockerignore
echo 'node_modules/
.git/
*.log
' > .dockerignore
Now rebuild:
docker build --no-cache --progress=plain . 2>&1 | grep -i context
Expected output:
#12 [internal] load build context
#12 transferring context: 108B 0.0s done
We dropped from 70 MB to ~108 bytes — a 99.999% reduction. Builds now take milliseconds instead of seconds, and no secrets or git history leak into the image layers.
Step 3: Using negation to keep specific files
Suppose you want to keep one .log file for debugging, but ignore all others:
echo '*.log
!important.log
' > .dockerignore
echo 'this is important' > important.log
echo 'debug output' > debug.log
Build again and check context — important.log is included, debug.log is not.
docker build --no-cache --progress=plain . 2>&1 | grep -i 'context transferred'
You'll see the context includes important.log (size depends on file).
Compare options / when to choose what
Sometimes you have multiple ways to reduce build context. Here's when .dockerignore wins versus alternatives.
| Approach | What it does | When to use |
|---|---|---|
.dockerignore |
Excludes files before the build starts | Always — it's the first defense and works with any Dockerfile |
COPY --chown targeting only needed dirs |
Copies only specific subdirectories inside Dockerfile | When you need fine-grained control over which directories appear in layers (but context still includes parent junk unless ignored) |
| Multi-stage builds | Discard intermediate stages to shrink final image | For production images, but does NOT reduce context size during build |
.gitignore + build scripts |
Sometimes used in CI to clean before build | Fragile — Docker daemon never sees the cleaned state; .dockerignore is explicit and portable |
Winner: Combine .dockerignore with multi-stage builds for both fast builds and small images. Never skip .dockerignore.
Troubleshooting & edge cases
Mistake 1: Ignoring directories that your Dockerfile actually needs
If you ignore a directory that appears in a COPY . instruction, Docker will NOT copy it, and your build may fail or produce an incomplete image.
Fix: Use ! negations to re-include specific sub-paths or files. Test by building with --progress=plain and watch for missing files.
Mistake 2: Pattern not working because of trailing slashes
node_modules (without slash) matches anywhere — file or directory. node_modules/ (with trailing slash) matches only directories. Use the slash form when you specifically want directories.
Mistake 3: Forgetting to add .dockerignore to version control
Your colleagues, CI, and future you won't have it. Always commit .dockerignore into your repository alongside the Dockerfile.
Edge case: Context is not the Dockerfile location
If you use docker build -f /path/to/Dockerfile ., the .dockerignore must be in the build context root (.), not next to the Dockerfile.
Edge case: Symlinks inside the context
Docker resolves symlinks before checking ignore patterns. If a symlink points outside the context, it can leak files. Better to keep symlinks inside the context or ignore them explicitly.
What you learned & what's next
You now understand how to use .dockerignore to reduce build context:
- It filters files before the build context is sent to the Docker daemon.
- Patterns follow .gitignore-like syntax with negation support.
- It directly improves build speed, reduces image size (indirectly), and eliminates security leakage.
- You tested before/after with concrete numbers (70 MB → 108 B).
Next steps: Now that your builds are fast and lean, the next lesson in the track is about optimizing Dockerfile caching — ordering layers and using --cache-from to reuse previously built layers. Keep your .dockerignore handy; even with perfect caching, sending unneeded files wastes time.
Final check: Before every build, ask yourself: “Does the daemon need my entire project history, dependencies, and personal notes?” No. Add
.dockerignoreand build with confidence.
Practice recap
Create a new project with a Dockerfile and a few large test files. Build without .dockerignore and note the context size. Then add .dockerignore that excludes test data and tmp/, rebuild, and compare the transferred size. Try using negation to include one specific test file. This hands-on exercise will solidify your understanding of context filtering.
Common mistakes
- Forgetting to add
.dockerignoreto version control — CI and teammates won't benefit from faster builds. - Ignoring a directory that the Dockerfile actually copies with
COPY .— the build will silently miss those files. - Using
/node_modules(absolute pattern) instead ofnode_modules/— absolute patterns are rarely correct for build contexts. - Not testing the pattern — run
docker build --no-cache --progress=plainand check the context transferred size.
Variations
- Use
.dockerignorewith pattern negation (!) to allow specific files in otherwise ignored directories. - Combine
.dockerignorewith multi-stage builds to keep context lean and final images small. - In CI, you can automatically generate
.dockerignorefrom.gitignoreusing tools likedockerignore-generator.
Real-world use cases
- Node.js project with huge
node_modules— reducing context from 200 MB to ~10 KB, cutting build time from 45s to 2s. - Python monorepo with
.venv,__pycache__, and.git— preventing secret leakage and securing build layers. - CI pipeline for a microservice where every second counts — using
.dockerignoreto keep context under 1 MB speeds up parallel builds.
Key takeaways
- .dockerignore is a declarative filter that prevents unwanted files from entering the build context.
- Always exclude
.git,node_modules,__pycache__, and other auto-generated or sensitive directories. - A single
.dockerignorecan reduce context size by 99%, dramatically speeding up builds. - Use
!negation to re-include specific files that would otherwise be ignored. - Test your
.dockerignorewith--progress=plainto see the actual context size transferred. - Commit
.dockerignorealongsideDockerfilefor consistent builds across all environments.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.