.dockerignore file: Optimizing Build Context
Back in the build lesson, there was a small promise tucked into a callout: “A .dockerignore file fixes this… we’ll put it to real use once we get to best practices.” This is that moment, pulled forward because it’s too useful — and too easy to get wrong by skipping — to leave for later.
Quick recap of the problem it solves: when you run docker image build, the entire build context — the folder you point that trailing . at — gets sent to the Docker daemon before the build even starts. Every file in that folder, whether your Dockerfile ever COPY’s it or not. A .dockerignore file is how you tell Docker “don’t even bother sending these.”
flowchart LR
subgraph Folder["Project folder"]
A[Dockerfile]
B[index.html]
C[.git/]
D[notes.txt]
E[secrets.env]
end
Folder -->|filtered by .dockerignore| Context["Build context actually sent"]
Context --> Daemon["Docker daemon"]
Writing One
A .dockerignore file lives right next to your Dockerfile, in the root of your build context. The syntax will look immediately familiar if you’ve ever written a .gitignore — one pattern per line, matched against paths relative to that root:
.git
*.md
*.log
node_modulesBuild from a folder containing this file, and none of the matched paths are sent to the daemon at all — not staged, not skipped-but-present, just never transmitted in the first place.
Trying It on the Nginx Project
Take the project from the Dockerfile-writing lesson. In reality, that folder probably isn’t just Dockerfile and index.html — it likely has a .git directory from version control, maybe a README.md, maybe some editor config files:
mysite/
├── Dockerfile
├── index.html
├── README.md
├── .git/
└── notes.txtNone of those extra files are wrong to have around — they’re just not part of what the image needs. A .dockerignore sitting alongside the Dockerfile keeps them out of the build entirely:
.git
README.md
notes.txtRun docker image build -t mysite:v1 . again, and the build context sent to the daemon now contains exactly two files: Dockerfile and index.html. Nothing about the resulting image changes — you were never COPY-ing .git or notes.txt in the first place — but the amount of data shipped to build it just got smaller, and stays smaller as that folder accumulates more unrelated stuff over time.
Why This Actually Matters
Build Speed
A .git directory can easily be tens or hundreds of megabytes once a project has some history behind it. None of that has any business being sent to the Docker daemon on every single build, and yet without a .dockerignore, it is — every time, whether the build takes advantage of the cache or not. On a small project this is barely noticeable. On a real one, with a deep git history, build logs, and stray downloaded assets sitting around, it’s the difference between a build that starts instantly and one that spends several seconds just packaging up files it will never use.
Not Accidentally Shipping What You Didn’t Mean To
This is the sharper reason to take .dockerignore seriously, not just the speed one. A broad COPY . . — which you’ve used plenty of times in this series — copies everything in the build context into the image, including things you almost certainly don’t want there: a .env file with real credentials, an SSH key sitting in the folder for unrelated reasons, a .git directory that contains your entire commit history.
Note
.dockerignore excludes files from the build context before any instruction runs — including COPY. If a file never reaches the daemon, no COPY . can pull it into your image by accident. This is one of the few places where a missing config file turns into a real, shippable security mistake, not just an inconvenience.
A Few Things That Trip People Up
Placement matters. .dockerignore has to sit in the root of the build context — the same folder as the trailing . you pass to docker image build. Put it somewhere else, and Docker won’t find it, silently.
It only affects what gets sent, not what’s already inside an image. If you already built and shipped an image containing a secret file because you didn’t have a .dockerignore at the time, adding one now and rebuilding doesn’t retroactively scrub that file from the old image. The old layers still exist, still contain that file, and are still sitting wherever that image was pushed. Prevention here beats cleanup.
Negation patterns exist, but read carefully. You can exclude a whole directory and then carve out one exception with !:
logs/*
!logs/.gitkeepThis says “ignore everything in logs/, except this one file.” Order matters here — a broad exclude followed by a narrower ! works; the reverse often doesn’t, since a parent directory excluded earlier can prevent Docker from even looking inside it for exceptions.
Wrapping Up
.dockerignore is a small file that solves two different problems at once — a faster build context, and a hard guarantee that files you never intended to ship can’t sneak into an image through a wide COPY .. It costs almost nothing to add, and the moment you actually need it is usually the moment you already forgot to add it — so the right time is now, before your next COPY . ., not after something ends up in an image it shouldn’t have.