.dockerignore files are your secret weapon for dramatically faster Docker builds by telling the Docker daemon precisely what not to send to the build context.
Let’s see this in action. Imagine you have a Node.js project. Without a .dockerignore, your docker build command would dutifully zip up your entire project directory, including node_modules, .git, logs, and potentially large development files, and send it over the wire to the Docker daemon. This transfer alone can be a massive bottleneck.
Here’s a typical package.json and a simple Dockerfile:
// package.json
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"express": "^4.17.1"
}
}
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "index.js"]
And a basic index.js:
// index.js
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});
Now, what happens if you don’t have a .dockerignore and your project has a huge node_modules directory (e.g., 500MB) and a .git directory (e.g., 100MB)? The COPY . . command in the Dockerfile will try to copy all of that into the build context. Even if you’re not explicitly copying node_modules or .git in your Dockerfile, they still get sent to the daemon as part of the build context.
The fix is to create a .dockerignore file in the same directory as your Dockerfile.
# .dockerignore
node_modules
.git
npm-debug.log
Dockerfile
.dockerignore
*.md
build
dist
With this .dockerignore in place, when you run docker build -t my-app ., Docker ignores the files and directories listed. The COPY . . command will now only see and copy the files that are not in .dockerignore. This means the build context sent to the daemon is drastically smaller, and the COPY operation itself is much faster.
Here’s the mental model: when you run docker build, your local Docker client gathers all files and directories in the specified build context (usually .) and creates a tarball (or similar archive) to send to the Docker daemon. The .dockerignore file, similar to .gitignore, dictates which files and directories are excluded from this tarball before it’s sent. This has two major benefits:
- Reduced Network Transfer: Less data to send from your client to the daemon, especially crucial if the daemon is remote or if you have a slow network.
- Faster
COPYOperations: TheCOPYinstructions in yourDockerfileoperate on a smaller set of files, making them complete much faster.
The most surprising thing about .dockerignore is how often it’s overlooked, even by experienced developers, leading to unnecessarily long build times and larger image layers. People focus on optimizing Dockerfile instructions like RUN or COPY, but neglect this fundamental step of controlling what even enters the build environment.
Consider the COPY package*.json ./ step. If you have a .dockerignore file that doesn’t exclude node_modules, even though you’re only copying package.json and package-lock.json, Docker still has to scan and potentially send node_modules to the daemon as part of the initial context if it’s not ignored. The .dockerignore applies to the entire build context, not just specific COPY commands. This means you should always include common build artifacts, dependencies, and VCS directories in your .dockerignore to minimize the initial context sent to the daemon.
The next concept you’ll run into is optimizing the layers created by your Dockerfile after you’ve tamed the build context.