Development

What Is a .dockerignore File? Why Docker Builds More Than You Think

Learn what a .dockerignore file is, what happens if you don't have one, how Docker builds using a build context, and why excluding unnecessary files improves security, performance and reproducibility.

What Is a .dockerignore File? Why Docker Builds More Than You Think

A .dockerignore file tells Docker which files and directories should not be included when building a container image. Every time you run docker build, Docker doesn’t immediately start executing the instructions in your Dockerfile. Instead, it first creates a build context, a snapshot of your project, and sends that snapshot to the Docker daemon. The .dockerignore file filters that snapshot before the build begins.

Without one, Docker sends almost everything in your project directory, including node_modules, .git, log files, editor settings, temporary files, local virtual environments, build outputs, and environment files containing secrets. Most of those files aren’t needed to build your image, yet Docker still has to process them unless you explicitly exclude them. For small projects you may never notice. For larger repositories, the difference can be substantial.

A well-written .dockerignore file produces faster builds, better cache performance, cleaner images, and reduces the risk of accidentally exposing sensitive information.

What Happens If You Don’t Have a .dockerignore File?

Many developers don’t create a .dockerignore file until they encounter slow builds or unexpectedly large images. Docker doesn’t require one. If the file doesn’t exist, Docker simply assumes every file inside the build context might be required.

Suppose your project looks like this:

project/
├── src/
├── node_modules/
├── .git/
├── .env
├── logs/
├── Dockerfile
└── package.json

Without a .dockerignore, Docker prepares all of it for the build:

Project Folder

Create Build Context

Everything Included
  • Source code
  • node_modules
  • Git history
  • Logs
  • Environment files
  • Temporary files

Send to Docker Engine

Even if your Dockerfile never copies those files into the final image, Docker has already spent time scanning, hashing, and transferring them. For small projects that overhead is minor. For repositories containing hundreds of megabytes, or even gigabytes, of dependencies, caches, and generated files, it quickly becomes noticeable. A .dockerignore file prevents those files from becoming part of the build context in the first place.

Docker Doesn’t Build Directly From Your Filesystem

One of the most common misconceptions is that Docker reads files directly from your project whenever it encounters a COPY instruction. That isn’t how the build process works, and the same “what’s actually visible at build time” question comes up in a related form in evolutive maintenance. Before Docker executes a single instruction in your Dockerfile, it creates a snapshot of the build context. Only that snapshot becomes available during the build.

The process looks more like this:

Project Folder

Read .dockerignore

Remove Excluded Files

Create Build Context

Send Build Context

Process Dockerfile

This ordering is important. The filtering happens before the Dockerfile is executed. If a file never enters the build context, it can never be copied into the image. Likewise, Docker never needs to hash it, cache it, or transfer it. That makes .dockerignore much more than a convenience feature: it defines the boundary between your development environment and the information Docker is allowed to build from.

The Build Context Is an Information Boundary

Thinking about .dockerignore as “a list of ignored files” is useful. Thinking about it as a boundary is even more useful, in much the same way that scoping what a system is even allowed to touch shows up as a recurring theme in AI-driven development lifecycles. Everything inside the build context becomes visible to the Docker build. Everything outside it effectively doesn’t exist. That distinction influences much more than build speed. It affects what can be copied into images, what participates in Docker’s cache calculations, what secrets might accidentally become accessible, and how reproducible builds remain across different development machines.

The Build Boundary

        Project Folder

        .dockerignore

════════════════════════
   Build Context Boundary


    Docker Build Context


         Docker Engine

Good container builds are built from the smallest amount of information necessary. The .dockerignore file is the mechanism that defines exactly what that information should be.

Why a .dockerignore File Matters

At first glance, excluding a handful of files might not seem particularly important, and on small projects it often isn’t. As projects grow, however, the build context grows with them. A JavaScript application might contain hundreds of megabytes of dependencies, a Git repository may contain years of history, and test reports, build artefacts, and temporary files accumulate over time. None of these files contribute to the container image, but without a .dockerignore file, Docker still has to consider them, and that affects far more than the final image size.

Faster Builds

Every build begins by creating the build context. The more files Docker needs to examine, the longer that process takes. Consider two projects containing exactly the same application code, where the only difference is that one contains a local node_modules directory with thousands of packages. Without a .dockerignore, Docker must process every one of those files before it can begin executing the Dockerfile. With a .dockerignore, they’re skipped immediately. The application hasn’t changed, but the build becomes noticeably faster because Docker has less information to process.

Better Build Cache Performance

One of Docker’s biggest strengths is its build cache. Whenever possible, Docker reuses work from previous builds instead of repeating it. To do that, Docker calculates checksums for files that participate in each build step. If those files change, Docker assumes the build step may also need to change; if they don’t, Docker can often reuse the cached layer.

Ignoring unnecessary files improves this process. Suppose your application never copies local log files into the image. Without a .dockerignore, those logs still exist inside the build context, and changes to them may force Docker to recalculate parts of the build unnecessarily. Once they’re excluded, Docker never considers them in the first place.

Cache Behaviour

   Local File Changes


   Included in Build?
     ┌────┴────┐
     ▼         ▼
    Yes        No
     │         │
Recalculate  Ignore
Build         File
Context


Potential
Cache Miss

A smaller build context generally produces more predictable cache behaviour because only relevant files influence the build.

Better Security

Development environments often contain information that should never become part of a container build, including .env files, private SSH keys, API credentials, local certificates, editor configuration, and cloud authentication files. If these files enter the build context, they become available to the Docker build. That doesn’t necessarily mean they’ll appear in the final image, but it does mean they’re now participating in the build process, which is worth being deliberate about, the same way test automation tools like Playwright are deliberate about what environment they run against.

The safest approach is to prevent sensitive files from entering the build context altogether. A .dockerignore file acts as one layer of defence by ensuring sensitive files are excluded before Docker begins processing the build. It shouldn’t replace proper secret management, but it significantly reduces the chance of accidentally exposing local development files.

Better Reproducibility

One benefit that’s often overlooked is consistency. Consider two developers building exactly the same project, where one has local debugging files and temporary logs sitting around, and the other recently cleaned their workspace. If those files become part of the build context, each developer may be building from slightly different inputs, which makes builds harder to reproduce. A carefully maintained .dockerignore file helps ensure every developer sends roughly the same project to Docker regardless of what happens to exist on their local machine. The build becomes more predictable because the inputs become more predictable.

A .dockerignore Doesn’t Make Images Smaller by Itself

This is one of the most common misunderstandings. Ignoring a file doesn’t automatically reduce the size of the final image, it reduces the size of the build context. Those aren’t necessarily the same thing.

Suppose your Dockerfile never copies node_modules. The dependencies won’t appear in the final image either way. Without a .dockerignore, Docker still had to process them while preparing the build, so the image size stays the same but the build takes longer. Now consider the opposite: if your Dockerfile copies the entire project with COPY . ., then excluding unnecessary files from the build context prevents them from ever reaching the image, so in that case both the build context and the final image become smaller.

The distinction matters. The primary purpose of .dockerignore is controlling what Docker builds from. Smaller images are often a consequence of making that boundary smaller rather than the goal itself.

Writing a .dockerignore File

The syntax used by .dockerignore is deliberately simple. Each line represents a pattern Docker should exclude from the build context.

Ignoring an entire directory is as simple as writing its name:

node_modules/

Ignoring an individual file works the same way:

.env

Wildcards allow multiple files to be excluded with a single rule:

*.log
*.tmp

Entire directory trees can also be ignored:

coverage/
dist/
build/

Docker evaluates these rules before creating the build context, so matching files never become available to the build.

Including Files Again

Occasionally you’ll want to ignore a group of files while keeping one specific file. Docker supports this using the ! operator, a pattern covered in more depth in Docker’s own build context documentation. Suppose you don’t want Markdown documentation copied into your image, except for the project README:

*.md
!README.md

Docker first ignores every Markdown file, and the second rule explicitly includes README.md again. Like many pattern-matching systems, rule order matters: the exception must appear after the rule it overrides.

A Practical .dockerignore

Most projects don’t require a particularly complicated ignore file. A typical JavaScript application might look something like this:

### Dependencies
node_modules/

### Git
.git
.gitignore

### Logs
*.log

### Environment variables
.env
.env.local

### IDE settings
.vscode/
.idea/

### Build output
dist/
build/

### Temporary files
tmp/
temp/

### Test coverage
coverage/

### Operating system files
.DS_Store
Thumbs.db

There’s nothing special about these entries. They simply represent files that normally don’t contribute to building the application. Your project may require more, or it may require fewer. The important question isn’t “what does everyone else ignore?” It’s:

“Does this file need to participate in the build?”

If the answer is no, it probably belongs in .dockerignore.

Language-Specific Examples

Different ecosystems generate different temporary files, but the purpose of .dockerignore remains exactly the same: exclude files that can be recreated or that aren’t required to build the application.

Node.js

node_modules/

npm-debug.log
yarn-error.log
pnpm-debug.log

coverage/

dist/

.git

.env

.vscode/

.idea/

Most Node.js applications install dependencies during the build. Copying an existing local node_modules directory usually increases the build context dramatically while providing little benefit.


Python

__pycache__/

*.pyc
*.pyo

.venv/
venv/

.pytest_cache/

.mypy_cache/

.git

.env

Python virtual environments belong to the development machine. Containers should create their own isolated environment during the build.


Java

target/

.gradle/

*.class

.idea/

.git

.env

Compiled classes and build outputs are typically recreated by the build process. There’s usually little value in sending them to Docker.


Go

bin/

coverage.out

.git

.env

.vscode/

As with the other examples, generated files are excluded because they can be recreated when the image is built.

Common Mistakes

Most .dockerignore problems don’t come from incorrect syntax. They come from excluding the wrong files, or not excluding enough of the right ones.

Ignoring Files Your Dockerfile Needs

If your Dockerfile contains COPY package.json ., then package.json must exist inside the build context. Ignoring it means Docker simply won’t be able to copy it, and the build will fail because the file never crossed the build boundary.

Copying Local Dependencies

Many developers accidentally copy their local dependencies into an image with a broad instruction like:

COPY . .

Without a suitable .dockerignore, this may also copy node_modules, build artefacts, editor settings, and temporary files. In many cases those files are unnecessary because the Dockerfile installs dependencies as part of the build, so sending them simply increases the amount of information Docker has to process.

Forgetting Environment Files

One of the most common omissions is .env. Local environment files frequently contain API keys, database passwords, authentication tokens, and development credentials. Even if they never end up in the final image, there’s rarely a good reason for them to become part of the build context. Ignoring them reduces the risk of accidental exposure.

Ignoring Too Much

The opposite mistake is excluding files that the build genuinely requires. Docker can only work with files present in the build context, so if an ignored configuration file is referenced by a COPY instruction, the build will fail because Docker cannot copy something it never received. A useful rule of thumb is simple: ignore files that are unnecessary, and keep files required to build the application. The goal isn’t the smallest possible build context, it’s the smallest correct build context.

.dockerignore vs .gitignore

One of the reasons developers sometimes misunderstand .dockerignore is that it looks almost identical to .gitignore. Both contain patterns, both exclude files, and both are stored in the root of the project. Despite those similarities, they solve completely different problems.

.dockerignore.gitignore
Controls the Docker build contextControls which files Git tracks
Used during docker buildUsed by Git
Reduces build context sizeKeeps repositories clean
Improves build performanceDoes not affect Docker builds

Some entries often appear in both files, for example .git, .env, .vscode/, and .idea/. That doesn’t make them interchangeable. Git only cares whether files belong in version control. Docker only cares whether files belong in the build context. A file ignored by Git can still be sent to Docker, and likewise a file ignored by Docker can still be committed to Git. Thinking of them as serving different stages of the development workflow makes their roles much clearer.

How Docker Processes a Build Context

Suppose your project looks like this:

project/
├── src/
├── node_modules/
├── .git/
├── .env
├── README.md
├── Dockerfile
└── package.json

And your .dockerignore contains:

node_modules/
.git
.env

Before Docker begins executing the Dockerfile, it creates the build context, and the ignored files are removed first. The resulting context becomes:

src/
README.md
Dockerfile
package.json

Everything else remains on your local machine. Docker never receives it.

What Docker Actually Sees

        Project Folder

       Read .dockerignore

════════════════════════
 Removed
   • node_modules
   • .git
   • .env
════════════════════════
 Remaining Build Context
   • src/
   • package.json
   • Dockerfile
   • README.md


         Docker Build

This is why .dockerignore improves both performance and security: Docker simply isn’t aware that the excluded files exist.

Best Practices

Most projects don’t need an extensive .dockerignore. A handful of carefully chosen rules usually provides the biggest benefit:

  • Ignore dependencies that are installed during the build.
  • Ignore version control directories such as .git.
  • Ignore IDE and editor configuration files.
  • Ignore temporary files and logs.
  • Ignore build artefacts that can be recreated.
  • Ignore local environment files containing secrets.
  • Keep the file under version control so the whole team shares the same build rules.
  • Review it whenever your project structure changes.

Rather than asking “what should I ignore?”, a better question is:

“Does Docker need this file to build the application?”

If the answer is no, it probably doesn’t belong in the build context.

Frequently Asked Questions

Does a .dockerignore file reduce image size? Sometimes, but not directly. Its primary purpose is to reduce the size of the build context. If ignored files would otherwise have been copied into the image, the final image will also become smaller. If they were never copied anyway, the image size won’t change, but the build will still be more efficient because Docker has fewer files to process.

Can I have multiple .dockerignore files? Yes. Most projects use a single .dockerignore file in the root of the build context, but Docker also supports Dockerfile-specific ignore files when a project contains multiple Dockerfiles:

Dockerfile
.dockerignore

build.Dockerfile
build.Dockerfile.dockerignore

test.Dockerfile
test.Dockerfile.dockerignore

When a Dockerfile-specific ignore file exists, Docker uses it instead of the root .dockerignore for that build.

Does .dockerignore support wildcards? Yes. Patterns such as *.log, *.tmp, build/, and coverage/ are commonly used, and Docker evaluates these patterns before creating the build context.

Can ignored files be included again? Yes, using the ! operator to create an exception:

*.md
!README.md

Docker first ignores every Markdown file before adding README.md back into the build context.

Should I ignore node_modules? Usually, yes. Most Dockerfiles install dependencies during the build, and copying a local node_modules directory often increases build time, creates larger build contexts, and can introduce platform-specific compatibility problems.

Should I ignore .git? In most cases, yes. Git history is rarely required during a container build, and excluding it reduces the build context and prevents unnecessary repository history from being sent to Docker.

Final Thoughts

At first glance, a .dockerignore file looks like a simple list of files to exclude. In practice, it plays a much more important role. Every Docker build begins by creating a build context, and that build context defines everything the Docker daemon is allowed to see. Without a .dockerignore file, Docker assumes every file in your project might be relevant, which means processing dependencies, logs, editor settings, Git history, temporary files, and other development artefacts that often have nothing to do with building the application.

A well-designed .dockerignore file creates a clear boundary between your development environment and your container build. The result is typically faster builds, more effective caching, improved reproducibility, reduced risk of exposing sensitive files, and cleaner, more predictable images.

Ultimately, a .dockerignore file isn’t really about ignoring files. It’s about defining exactly what information is allowed to participate in the build. The smaller and more deliberate that boundary becomes, the more reliable your container builds will be.

Written by the Workshelve team, who write practical explainers on data integrity, networking, and developer tooling.

Top