Key idea
docker build . first sends the whole folder, the build context, to the builder. COPY . . then copies all of it into the image, .env and .git included. A .dockerignore file lists what never leaves your folder, the way .gitignore lists what never reaches the repository.
What the dot sends
The . at the end of docker build -t my-app . is the build context. Every COPY reads from it, and nothing outside it can be copied at all.
Take a small app folder with an .env file, a .git folder and 50 MB of installed dependencies, and a Dockerfile that runs COPY . .. The build output shows what it sent:
#6 transferring context: 50.04MB 0.3s done
And the image holds the key, for anyone who can pull it:
docker run --rm my-app cat .env
API_KEY=demo-not-a-real-key-0000
That's the image leak from How secrets leak. Deleting the file in a later RUN step doesn't help: the layer that copied it keeps it.
A listing of the app folder inside the image shows everything that came along:
docker run --rm my-app ls -a
.
..
.env
.git
Dockerfile
app.js
node_modules
Only app.js and the dependencies the build installs belong there. Make this listing a habit before you push an image anywhere.
.dockerignore
Put a file called .dockerignore next to the Dockerfile, one pattern per line:
.git
.env
.env.*
node_modules
*.log
Build again and the context drops to almost nothing:
#4 transferring context: 130B done
.env is no longer in the image, and the build doesn't wait while 50 MB is copied. The transferring context line is worth a glance on every build: megabytes for a small app mean something in the folder should be ignored.
What to list
- Secrets:
.envand any key or credentials files. Give them to the container at run time with-eor--env-fileinstead. - History and tooling:
.git, editor folders, test output. - Local data: database dumps, uploads, logs. They're often the largest files in the folder, and can hold customer data.
- Things the build makes itself:
node_modules,__pycache__,dist. Copying in your laptop's copy can also break the image, since packages built for macOS don't run on Linux.
An unlisted file isn't always a problem. It becomes one when a broad COPY . . picks it up, so pair the ignore file with narrow copies where you can: COPY package.json package-lock.json ./ before COPY . ..
Allow-list style, and where the file must live
Instead of listing what to leave out, you can exclude everything and add back what you need:
*
!package.json
!package-lock.json
!server.js
Docker looks for .dockerignore at the root of the build context. If you build a subfolder (docker build ./api), the ignore file goes in that subfolder.
Check yourself