.dockerignore reference

Last reviewed on 2026-05-02

Pattern syntax, precedence rules, common mistakes, and how it interacts with the build context, COPY, and ADD.

The .dockerignore file sits in the root of the build context and tells the Docker CLI which files and directories should not be sent to the daemon when a build is started. It is one of the highest-leverage files in any Docker project: a careful .dockerignore can shave gigabytes off the build context, prevent secrets from being copied into images, and keep the layer cache stable across machines.

Where it goes

The file is named exactly .dockerignore (with the leading dot) and lives in the root of the build context — usually next to the Dockerfile at the top of your repository. When you invoke docker build ., the CLI looks for a .dockerignore in the current directory; if you pass an explicit context path, it looks there.

Recent BuildKit versions also support a per-Dockerfile ignore file: if you build with docker build -f services/api/Dockerfile services/api, BuildKit will check for services/api/Dockerfile.dockerignore first and only fall back to a root .dockerignore if the per-Dockerfile file is absent. This lets a monorepo give each service its own ignore rules.

Syntax

Each non-empty, non-comment line is a pattern. Comments begin with #. Patterns use the same matching rules as Go's filepath.Match, with three additions: a leading ! negates a pattern, a leading ** matches any number of directories, and patterns are matched against paths relative to the build context root.

PatternMatches
node_modulesThe directory node_modules at the root of the context, recursively.
**/node_modulesAny node_modules directory at any depth.
*.logAny file ending in .log at the root of the context.
**/*.logAny file ending in .log at any depth.
build/Equivalent to build; trailing slash is allowed but not required.
!important.logRe-include important.log even if a previous rule excluded it.
secrets/**Everything inside secrets/ at any depth.

Precedence and order of evaluation

Rules are evaluated in order, and the last matching rule wins. This is the single most common source of confusion. To exclude everything except a specific subdirectory, you exclude broadly first and then re-include with !:

# exclude everything
*

# but include these specific files
!Dockerfile
!package.json
!package-lock.json
!src/**

Re-inclusion does not work across directories that are themselves excluded. If you exclude src with the bare pattern src, you cannot re-include src/index.js, because the parent has already been pruned. Use the directory-glob form src/** when you want to allow exceptions inside it.

What it actually skips

A common misconception is that .dockerignore only filters what gets COPYed. It does more than that: it filters the build context itself, before the daemon ever sees it. That has three consequences:

Worked example: a typical Node.js project

# Dependencies and build artefacts
node_modules
**/node_modules
dist
build
coverage
.next
.cache

# Logs and editor noise
*.log
*.swp
.DS_Store
.idea
.vscode

# Secrets and local config
.env
.env.*
!.env.example
secrets/**
*.pem
*.key

# Git and CI
.git
.gitignore
.github

Note the deliberate use of !.env.example: the rule .env.* would otherwise sweep it up. The last-match-wins rule lets you check in a sample env file without exposing real ones.

Common mistakes

Checklist before committing your .dockerignore

  1. Run docker build . and look at the "transferring context" line — it should be measured in MB, not GB.
  2. Inspect the resulting image with docker run --rm -it <image> ls -la / or docker history and confirm no .git, no editor backups, and no environment files appear.
  3. Search the image for known secret filenames: docker run --rm <image> find / -name '.env*' -o -name '*.pem' 2>/dev/null.
  4. Commit the .dockerignore. Don't gitignore it.

Related