.dockerignore
A .dockerignore file tells Docker which files to leave out of the build context. It matters because Docker does not build from your working directory directly; it first sends a selected set of files to the builder, and every unnecessary file can slow builds, weaken cache reuse, or accidentally expose secrets.
Think of .dockerignore as the build-context filter for Docker. It is similar in spirit to .gitignore, but it controls what the Docker builder can see, not what Git tracks.
Overview: How .dockerignore Works
When you run docker build -t myapp:1.0 ., the final . is the build context path. The Docker client walks that directory, applies ignore rules from .dockerignore, and sends the remaining files to the builder. With modern BuildKit, Docker is smarter about transferring only what the build needs, but the build context is still the boundary: files excluded by .dockerignore are not available to COPY or ADD.
This is separate from image layers. A COPY . . instruction creates a layer based on files that survived the ignore filter. If you do not ignore node_modules, .git, test reports, local databases, or large generated folders, Docker may hash and transfer them as part of cache decisions. Even if a later instruction deletes those files, they may already have existed in an earlier image layer. That is why .dockerignore is both a performance tool and a safety tool.
The file is named exactly .dockerignore and is usually placed at the root of the build context, beside the Dockerfile. Docker also supports Dockerfile-specific ignore files named like Dockerfile.dockerignore for a Dockerfile named Dockerfile, or build.Dockerfile.dockerignore for build.Dockerfile. The normal root .dockerignore is what you will use most often.
A key point: .dockerignore does not change the host directory, delete files, or change what Git tracks. It only changes what is sent to the Docker builder. If an ignored file is required by COPY, the build fails because the file is not in the context.
Syntax
A .dockerignore file contains one pattern per line:
# comment
pattern
directory/
*.extension
!exception
| Pattern | Meaning |
|---|---|
# comment |
Ignored by Docker. Use comments to explain unusual rules. |
node_modules |
Ignore a file or directory named node_modules anywhere below the context. |
dist/ |
Ignore a directory named dist. |
*.log |
Ignore files whose names end in .log. |
**/coverage |
Ignore a coverage directory at any depth. |
!README.md |
Re-include a file that was previously ignored, when its parent directory is still available. |
Patterns are evaluated from top to bottom, and later rules can override earlier rules with !. Be careful with exceptions: if you ignore an entire parent directory, Docker cannot re-include a child file unless the parent path itself is also available.
Examples
Example 1: Ignore Common Local Files
For a small Node.js app, a useful first .dockerignore removes dependencies, logs, Git metadata, and local environment files from the context:
node_modules
.git
*.log
coverage
.env
.DS_Store
Build the image with a pinned base image in the Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
CMD ["node", "server.js"]
docker build -t ignore-demo-api:1.0 .
Output:
[+] Building 6.8s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
=> [internal] load .dockerignore
=> [internal] load metadata for docker.io/library/node:20-alpine
=> [1/5] FROM docker.io/library/node:20-alpine
=> [2/5] WORKDIR /app
=> [3/5] COPY package*.json ./
=> [4/5] RUN npm ci --omit=dev
=> [5/5] COPY . .
=> naming to docker.io/library/ignore-demo-api:1.0
Docker reports that it loaded .dockerignore before copying files. The local node_modules directory is not sent to the builder, so the image gets dependencies from the reproducible npm ci step instead of whatever happened to be installed on your laptop.
Example 2: Preserve Cache By Ignoring Generated Output
Generated folders are a common cause of cache misses. If your app writes dist, coverage, and temporary files during local development, ignore them:
dist/
coverage/
tmp/
*.tmp
npm-debug.log*
docker build -t cache-clean-web:1.0 .
docker build -t cache-clean-web:1.1 .
Output:
[+] Building 1.1s (10/10) FINISHED
=> [internal] load .dockerignore
=> CACHED [2/5] WORKDIR /app
=> CACHED [3/5] COPY package*.json ./
=> CACHED [4/5] RUN npm ci --omit=dev
=> CACHED [5/5] COPY . .
=> naming to docker.io/library/cache-clean-web:1.1
The second build can reuse cache because irrelevant generated files did not become inputs to COPY . .. If coverage/ changed after every test run and was still inside the context, Docker would treat the copied source layer as different.
Example 3: Keep Documentation While Ignoring Most Markdown
Sometimes you want broad ignore rules with a small exception. This example excludes Markdown files except the main README:
*.md
!README.md
If the Dockerfile copies the README, it is still available:
FROM alpine:3.20
WORKDIR /docs
COPY README.md ./
CMD ["cat", "README.md"]
docker build -t docs-demo:1.0 .
Output:
[+] Building 0.9s (7/7) FINISHED
=> [internal] load .dockerignore
=> [1/3] FROM docker.io/library/alpine:3.20
=> [2/3] WORKDIR /docs
=> [3/3] COPY README.md ./
=> naming to docker.io/library/docs-demo:1.0
The exception works because README.md is a file at the context root. If you ignored an entire directory such as docs/, then tried !docs/README.md, you would also need to avoid excluding the parent path too aggressively.
How It Works Step By Step
- You run
docker buildwith a context path such as.. - The Docker client looks for
.dockerignorein that context. - Docker evaluates the ignore patterns and removes matching files from the context view.
- The filtered context is made available to the builder. In remote daemon setups this can reduce network transfer; with Docker Desktop the builder runs inside the Desktop VM, so less context still means less data to package, hash, and send.
- The builder reads the Dockerfile. Instructions such as
COPYandADDcan only use files that remain in the filtered context. - For cache checks, Docker compares each instruction and the relevant inputs. If a copied file changes, the
COPYlayer changes and every later layer is invalidated. - The final image contains only what the Dockerfile added. A good
.dockerignoremakes it harder to accidentally add local-only files in the first place.
This is why .dockerignore and Dockerfile order work together. Ignore files that should never be build inputs, then order COPY instructions so slow dependency layers depend on lockfiles, not the entire source tree.
Common Mistakes
Ignoring Nothing
A missing .dockerignore often means Docker sees everything: Git history, local dependency directories, editor files, logs, and possibly secrets.
# Wrong: no .dockerignore file exists
The fix is to start with a small, explicit ignore file:
.git
node_modules
*.log
.env
coverage
dist
Expecting .dockerignore To Remove Files After COPY
.dockerignore filters the build context before the Dockerfile runs. It cannot remove files already copied from a previous build stage or created by RUN. If a secret is copied into a layer, deleting it later does not erase it from that earlier layer.
FROM alpine:3.20
WORKDIR /app
COPY . .
RUN rm -f .env
CMD ["ls", "-la"]
The fix is to keep the secret out of the context so COPY . . can never add it:
.env
*.pem
*.key
secrets/
Ignoring Files The Build Needs
If you ignore a file and then try to copy it, the build fails:
package-lock.json
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
Output:
ERROR: failed to calculate checksum of ref: "/package-lock.json": not found
The fix is not to ignore lockfiles. Lockfiles make dependency installs reproducible and help the cache behave predictably.
Best Practices
- Always create a
.dockerignorefor application images, even small projects. - Ignore dependency directories that should be installed inside the image, such as
node_modulesor language-specific virtual environments. - Ignore VCS metadata such as
.git; it is usually large and changes often. - Ignore secrets, local config, certificates, and environment files. Do not bake secrets into images.
- Ignore generated output such as
dist,coverage,target, and logs unless the Dockerfile intentionally needs them. - Keep
package-lock.json,go.sum,poetry.lock, and similar lockfiles in the context when builds use them. - Use specific base image tags such as
node:20-alpineoralpine:3.20; avoidlatestfor reproducible builds. - Pair
.dockerignorewith cache-friendly Dockerfiles: copy dependency manifests first, install dependencies, then copy source. - Review ignore rules when adding new tooling because build output folders and secret file names vary by stack.
Practice Exercises
- A Python project has
.venv/,__pycache__/,.pytest_cache/,requirements.txt, andapp.py. Write a.dockerignorethat excludes local caches but keeps the dependency manifest. - A Node.js project rebuilds slowly after every test run because
coverage/changes. Add ignore rules and adjust the Dockerfile sonpm cidepends only on package manifests. - A team accidentally included
.envandprivate.pemin a build context. Write ignore rules that prevent this and describe how you would rotate the exposed credentials.
Summary
.dockerignorefilters files before Docker sends the build context to the builder.- Ignored files cannot be used by
COPYorADD. - Smaller contexts usually mean faster builds and fewer unnecessary cache invalidations.
- Ignoring secrets is essential because deleting a copied secret later does not remove it from earlier layers.
- The best results come from combining
.dockerignorewith deliberate Dockerfile layer ordering.
