TechEarl

.dockerignore Best Practices

The .dockerignore file controls what COPY . . actually ships into your image. Without it, your image gets node_modules, .git, .env, and every other thing in the project. With it, builds are faster, images smaller, and secrets stay out.

Ishan KarunaratneIshan Karunaratne⏱️ 6 min readUpdated
Share thisCopied

.dockerignore is the single most underrated file in a Dockerfile setup. Add a reasonable one and your image gets smaller, your builds get faster, your node_modules from the host stops poisoning the container, and secrets in .env don't accidentally end up in a published image. Skip it and COPY . . ships everything in the project directory — typically hundreds of MB of stuff you don't need.

What it does

.dockerignore lives next to your Dockerfile (in the build context root). Before docker build sends the context to the daemon, the daemon applies the ignore rules. Anything matched is excluded from the build context entirely — invisible to COPY and ADD, not transferred over the wire, never in the image.

Same syntax as .gitignore (with small differences noted below).

A reasonable baseline

For a typical web project, this is the starting point:

code
# Version control
.git
.gitignore
.gitattributes

# Editor / IDE / OS
.vscode
.idea
.DS_Store
Thumbs.db
*.swp
*.swo

# Build artifacts (regenerated in the image)
dist
build
.next
out
.nuxt
*.log

# Dependency directories (regenerated in the image)
node_modules
vendor
.venv
venv
__pycache__
*.pyc
*.pyo

# Test and coverage
coverage
.nyc_output
.pytest_cache
.mypy_cache
.ruff_cache

# Local env (NEVER ship)
.env
.env.local
.env.*.local

# Docker artifacts
Dockerfile
.dockerignore
docker-compose*.yml

# Docs (usually not needed in runtime images)
README.md
LICENSE
CHANGELOG.md

Drop that into .dockerignore at your project root and you've already done 90% of the work.

Per-language additions

Node.js — the standard baseline above plus:

code
.npmrc
.yarnrc
.pnp.*

Python:

code
.tox
.eggs
*.egg-info
poetry.lock.bak
.python-version

Go:

code
*.exe
*.test
*.out
go.sum.bak

PHP / Laravel:

code
vendor
storage/logs/*
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/views/*
public/storage

Rust:

code
target
**/*.rs.bk

Why each entry matters

node_modules — biggest single win. Without this, COPY . . copies your host's node_modules (potentially native modules compiled for the wrong OS/architecture) on top of whatever was installed inside the container. Images explode in size and break in subtle ways.

.git — on a project with any history, the .git directory is often 50-500 MB. Useless inside the image; ship without it.

.env — secret database passwords, API keys, OAuth tokens. Should never end up in an image (which can be pushed to a registry and pulled by anyone with access). Pass env vars at runtime via docker run -e or Compose environment: / env_file:.

Build artifacts (dist, build, .next, out) — your image's build stage runs npm run build (or equivalent) and produces these. Whatever is in the host's dist/ from an old build is wrong and confusing. Always rebuild inside the image.

Dockerfile and .dockerignore — the files used to build the image aren't useful inside the image itself. Tiny but ideologically correct to ignore.

Pattern syntax

Same as .gitignore, with the same wildcards:

code
*.log           # any .log file at any directory level
**/*.log        # explicit — same as *.log
build/          # a directory called build at the root
**/build/       # build directory at any level
!important.log  # negation — re-include something matched earlier

! negation is useful for "ignore everything in this directory except…":

code
storage/*
!storage/.gitkeep

Order matters. The first matching rule wins.

Inspecting what gets sent

bash
docker build --no-cache --progress=plain -t test . 2>&1 | head -20

The first lines of build output show transferring context: with the size. A "transferring context: 1.5GB" message is a strong sign you need a .dockerignore.

For more detail, BuildKit's plain progress reports each step's input size. You can also use docker buildx bake --progress=plain for the same output.

Common pitfalls

  • Forgetting to add node_modules (or vendor, venv, __pycache__). The biggest single contributor to oversize images and slow builds. Even with multi-stage, the build context still copies in slow if it's huge.
  • Not excluding .env. Ends up in image history, pushed to a registry, readable by anyone with image-pull access.
  • Negation patterns getting ignored. Pattern order matters. If you *.log then !important.log, important.log is re-included. If you reverse the order, *.log matches it and the negation is too late.
  • Trailing slashes. build/ matches only directories named build. build matches both files and directories.
  • Relative paths confusion. .dockerignore uses paths relative to the build context, not relative to where you ran docker build from. Usually the same, but if you docker build -f path/to/Dockerfile path/to/context and don't notice, the ignore rules apply to the context path, not your working directory.
  • Leading slashes. Don't use them. /build and build mean different things in .gitignore but in .dockerignore only build (no leading slash) matches at the root. Just write build/ and **/build/ if you want both.

Compose-specific

Compose passes a build context to Docker the same way docker build does. The .dockerignore in the build-context root applies. If you have multiple services with different build: contexts, each gets its own .dockerignore next to its Dockerfile.

What to do next

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsDocker.dockerignoreDockerfileBest PracticesDevOps

Found this useful? Pass it on.

Copied
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

D

Docker Restart Policies and Health Checks

Make containers come back automatically after crashes and reboots, and tell Compose how to wait until a service is actually ready (not just started). Restart policies, HEALTHCHECK, and depends_on: condition: service_healthy.

H

How to Dockerize a Node.js App

A Dockerfile for a real Node.js app: multi-stage build, npm ci for deterministic dependencies, the node_modules-volume trick that makes bind-mounted source fast on Mac, and the non-root user that most tutorials skip.

D

Docker Cheat Sheet

The Docker commands I actually use, grouped by job: images, container lifecycle, run flags, exec and logs, build, networks, volumes, Compose, registry, and the prune/inspect commands for keeping a host clean.