A .dockerignore file is a configuration file that tells Docker which files and directories to exclude from the build context when creating an image. It works before Dockerfile instructions such as COPY and ADD use that context, so an ignored file is unavailable to the build rather than simply skipped when the final image is assembled.
That makes .dockerignore useful for more than tidying up a project. Excluding unnecessary dependencies, Git metadata, logs, local configuration, and build artifacts can reduce the amount of data Docker has to process, improve build and cache behavior, prevent accidental copies, and reduce the chance that sensitive local files become available during a build.
The basic flow is:
Project directory
│
▼
Docker build context
│
▼
.dockerignore
│
├── Included files ──► sent to builder ──► available to COPY / ADD
│
└── Ignored files ───► excluded ─────────► unavailable during build
The important concept is the build context. Once you understand what Docker can and cannot see, most .dockerignore behavior follows naturally.
How .dockerignore Controls the Build Context
When you run:
docker build -t my-app .
the final . identifies the build context. In this example, Docker is building from the current directory.
Suppose the project looks like this:
my-app/
├── .git/
├── node_modules/
├── src/
├── dist/
├── logs/
├── .env
├── package.json
├── package-lock.json
├── Dockerfile
└── .dockerignore
Not everything in that directory needs to participate in the image build. node_modules may contain thousands of local dependency files, .git contains repository metadata, logs contains runtime output, and .env may contain local configuration or secrets.
A .dockerignore file can exclude them:
.git/
node_modules/
logs/
.env
dist/
Docker applies those rules while preparing the build context. The builder receives the remaining files rather than treating every file in the project directory as useful build input.
This has an important consequence for the Dockerfile. If a file is included in the context, instructions such as COPY and ADD can use it; if .dockerignore excluded it, those instructions cannot retrieve it later.
For example:
Build context after filtering
src/ ✓ available
package.json ✓ available
package-lock.json ✓ available
.git/ ✗ ignored
node_modules/ ✗ ignored
logs/ ✗ ignored
.env ✗ ignored
dist/ ✗ ignored
If the Dockerfile later contains:
COPY . .
the ignored files are not suddenly restored. COPY operates on the context Docker has available.
The same rule explains a common build error. If .dockerignore contains:
dist/
but the Dockerfile expects:
COPY dist/ /app/
the build cannot copy dist because it was excluded before the instruction ran. Whether dist is visible in your local project directory is irrelevant at that point; it is not available in Docker’s build context.
Why You Should Use a .dockerignore File
The most immediate reason to use .dockerignore is build performance.
Development directories frequently contain far more data than an image needs. A local node_modules directory can contain tens of thousands of files, while .git can become substantial after years of repository history. Test results, caches, generated files, and build artifacts add more.
Excluding that material gives Docker a smaller, more focused input:
Without filtering
source + dependencies + Git history + logs + caches + secrets + artifacts
│
▼
Docker build
With .dockerignore
required build files
│
▼
Docker build
A smaller context means less unnecessary data for the build process to handle, which can be particularly useful when the builder is remote rather than running entirely on the developer’s machine.
Cleaner input also helps make cache behavior more predictable, avoiding the kind of hidden input changes that lead to configuration drift. Docker’s cache works best when build steps depend only on files that genuinely matter to those steps; unrelated files changing in the project should not create unnecessary build work.
A Dockerfile might deliberately copy dependency manifests before application source:
COPY package.json package-lock.json ./
RUN npm ci
COPY src/ ./src
RUN npm run build
This allows the dependency installation step to remain reusable when application source changes but the manifests do not. .dockerignore supports the same general strategy by keeping unrelated project files out of the build context in the first place, which aligns with Docker’s guidance on build cache optimization.
It also reduces accidental copying. A broad instruction such as COPY becomes much less dangerous when the context has already been filtered to remove files that never belong in the build.
Common Files and Folders to Exclude
A typical .dockerignore focuses on files that are large, local to the development environment, generated elsewhere, or unsafe to expose to the build.
For a Node.js application, a starting point might look like:
# Version control
.git/
.gitignore
# Local dependencies
node_modules/
# Logs
*.log
logs/
# Local environment files
.env
.env.*
# Test output
coverage/
# Editor files
.vscode/
.idea/
.DS_Store
# Temporary files
tmp/
temp/
node_modules is a particularly common exclusion. A container normally installs its own dependencies during the build, and locally installed packages may contain platform-specific binaries or other artifacts that do not belong in the container environment.
.git is another common candidate because repository history and metadata are rarely needed to run or build the application. Logs, test output, editor configuration, and temporary directories usually provide similarly little value to the image build.
Build artifacts such as dist/, build/, or target/ require more thought. Whether they should be ignored depends on where the project expects those artifacts to be created.
If a TypeScript application compiles inside Docker, for example, local dist/ output can usually be excluded:
COPY package.json package-lock.json ./
RUN npm ci
COPY src/ ./src
RUN npm run build
If a CI system builds the application first and Docker deliberately packages that output:
COPY dist/ /app/
then dist/ must remain available in the build context.
There is no universal .dockerignore file that is correct for every project. The right exclusions depend on what the Dockerfile genuinely needs as input.
.dockerignore Helps Reduce Secret Exposure
Local projects also contain files that should never be copied into container images.
Environment files are an obvious example:
.env
.env.local
.env.production
Projects may also contain private keys, credentials, certificates, database dumps, or backups:
.env
.env.*
*.pem
*.key
secrets/
backups/
credentials.json
Excluding these files reduces the chance that a broad COPY . . instruction makes them part of a build unintentionally.
More importantly, it reduces what Docker can see in the first place.
Local project
│
├── source code ─────────► build context
├── package manifests ───► build context
│
├── .env ────────────────X
├── private.key ─────────X
└── secrets/ ────────────X
.dockerignore
This is a useful security guardrail, but it is not a replacement for proper secret management. If a build genuinely requires temporary credentials, those credentials should be provided through an appropriate build mechanism rather than deliberately included in the context and removed afterward.
The simpler rule is that a sensitive local file that the build does not need should not be part of the build context.
How .dockerignore Patterns and Exceptions Work
.dockerignore uses path patterns to decide what should be excluded.
Simple entries can ignore specific files or directories:
node_modules/
.git/
.env
Wildcards can cover groups of files:
*.log
*.tmp
This is useful when generated files can appear under different names but share a predictable pattern.
The ! syntax creates an exception to an exclusion. For example:
*.md
!README.md
This excludes matching Markdown files while retaining README.md.
Exceptions are useful when a broad pattern is convenient but a particular file still needs to remain available to the build. They should be kept understandable, however, because complicated combinations of exclusions and exceptions can make it difficult to determine what the builder actually receives.
Comments can help document why a rule exists:
# Dependencies are installed inside the image.
node_modules/
# Local credentials must not enter the build context.
.env
.env.*
# Repository history is unnecessary for this image.
.git/
The goal is not to demonstrate every pattern .dockerignore supports. The goal is to describe the intended build context clearly enough that another developer can understand why each major exclusion exists.
.dockerignore vs .gitignore
.dockerignore and .gitignore look similar, and many projects contain overlapping rules. They control different systems.
The distinction is straightforward:
.gitignore
│
└── controls what Git tracks
.dockerignore
│
└── controls what Docker sees during a build
.gitignore tells Git which untracked files it should normally ignore. .dockerignore tells Docker which files should be excluded from the build context.
That means a file ignored by Git is not automatically ignored by Docker.
For example, a project may contain:
# .gitignore
.env
node_modules/
This helps prevent those files from being committed to the repository, but it does not define Docker’s build context. The Docker build needs its own exclusions.
The reverse can also be intentional. A file can belong in Git but have no reason to enter a Docker build.
Documentation is a simple example:
docs/
README.md
Those files may belong in source control while being unnecessary input for a particular production image.
Generated artifacts demonstrate the opposite possibility. dist/ might be ignored by Git because CI generates it, while a Dockerfile intentionally consumes the CI-produced directory when assembling an image.
The two files therefore answer different questions:
| File | Controls | Main question |
|---|---|---|
.gitignore | Git | Should Git normally track this local file? |
.dockerignore | Docker build context | Should Docker receive this file during the build? |
Copying .gitignore into .dockerignore can provide a starting point, but treating them as interchangeable misses the different boundaries they protect.
A Good .dockerignore Makes Builds More Predictable
A useful .dockerignore does not need to be large. It needs to reflect what the image actually requires.
Start with the build context and divide its contents into two groups:
Build context
│
├── Required by the build
│ │
│ ▼
│ keep available
│
└── Not required
│
▼
consider excluding
Large local dependency directories, Git metadata, logs, caches, editor configuration, temporary files, and local secrets are usually straightforward candidates. Generated artifacts need to match the project’s build strategy rather than being ignored automatically, especially when restartable delivery processes rely on knowing which step created which output.
The same reasoning provides a useful review process when the project changes. If a Docker build becomes unexpectedly slow, inspect whether the context has grown; if an unwanted file appears in an image, ask why the build could see it, and if COPY suddenly cannot find a file, check whether the selected context or .dockerignore removed it. The same investigation habit helps with configuration drift, where the visible failure often comes from an earlier boundary becoming unclear.
That keeps .dockerignore tied to its real purpose rather than treating it as forgotten boilerplate.
A .dockerignore file ultimately controls what Docker sees during a build. By filtering the build context before Dockerfile instructions consume it, the file can make builds smaller and faster, reduce unnecessary cache disruption, prevent accidental copies, and limit exposure of local secrets, much as structured logging makes operational input more deliberate instead of accidental.
The distinction from .gitignore follows from the same idea: .gitignore controls what Git tracks; .dockerignore controls what Docker receives as build input. A well-designed .dockerignore therefore creates a cleaner, safer, and more predictable boundary around the files used to build an image, which matters when real browser tests or CI jobs need builds to behave consistently.





