Blog
3 min read

.dockerignore Explained: Faster Builds, Smaller Images, No Leaked Secrets

A .dockerignore file keeps files out of the Docker build context: node_modules, .git, .env files, build output. Why that makes builds faster and images smaller, how it prevents secrets ending up in images, the syntax and negation rules, a ready-to-use template, and how to check what's being sent.

When you run docker build ., Docker first packages up the folder you pointed it at — the build context — and sends it to the builder. Every COPY . . in your Dockerfile copies from that context.

A .dockerignore file lists what to leave out. Without one, Docker sends everything: your node_modules, your whole .git history, and your .env file full of secrets. (Dockerfile Explained)

Why it matters

1. Secrets end up in images. COPY . . with no .dockerignore copies .env, private keys and credentials files into the image. Anyone who can pull the image can read them — even if a later step deletes the file, it's still in an earlier layer. (Docker Image Layers and OverlayFS)

2. Builds are slow. Sending a 500 MB node_modules or a large .git folder to the builder on every build wastes time before anything happens.

3. Caching breaks. If irrelevant files (logs, editor settings, git metadata) are in the context, they change constantly, and any COPY . . layer gets rebuilt for no reason.

4. Images are bigger and less reproducible. Your local node_modules (built for your Mac) can overwrite the one installed inside the Linux image. (Exec Format Error)

A good starting template (Node.js)

# .dockerignore
.git
.gitignore
node_modules
npm-debug.log*
.next
dist
build
coverage
.env
.env.*
!.env.example
*.pem
*.key
.DS_Store
.vscode
.idea
Dockerfile*
docker-compose*.yml
README.md

For Python, swap in __pycache__, *.pyc, .venv, .pytest_cache and .mypy_cache.

Syntax

  • One pattern per line, relative to the build context root.
  • * matches anything except /; ** matches any number of directories (**/*.log).
  • # starts a comment.
  • ! re-includes something an earlier line excluded — order matters, the last matching line wins:
.env*
!.env.example
  • Unlike .gitignore, a pattern like node_modules only matches at the context root. Use **/node_modules to catch nested ones in a monorepo.

Check what's actually being sent

The build output shows the context size:

=> [internal] load build context
=> => transferring context: 2.14kB

Kilobytes or a few megabytes is healthy. Hundreds of megabytes means something big isn't ignored.

To inspect the final image for files that shouldn't be there:

docker run --rm -it myapp:latest sh -c "ls -la /app; ls -la /app/.env* 2>/dev/null"

It doesn't replace good Dockerfile habits

  • Copy dependency manifests first, install, then copy source — so dependency layers stay cached:
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

If a secret already went into an image

Assume it's exposed: rotate the secret, rebuild without it, and delete the old image tags from your registry. (Leaked API Key: What to Do)

The summary

  • .dockerignore keeps files out of the build context.
  • Always ignore .git, node_modules, build output and every .env file.
  • Check the "transferring context" size, and never rely on deleting secrets in a later layer.

EasySpawn keeps your app's secrets in server-side environment variables rather than in images or repos, and Claude Code can audit your Dockerfile and .dockerignore for leaks. See how it works or join the waitlist.

Related: What Is Docker? · Docker Image vs Container · .gitignore Explained · Docker Commands Cheat Sheet

Keep reading