Learning paths / Containers / Writing Dockerfiles

.dockerignore and build context

Reading · 5 min · Module 4, lesson 4 of 635 min left in this module

Module 4 · Writing DockerfilesLesson 4 of 6

Goal: Use a .dockerignore file to keep secrets and junk out of the build context and the image.

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: .env and any key or credentials files. Give them to the container at run time with -e or --env-file instead.
  • 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

Your .gitignore lists .env, so the key has never been committed. Your Dockerfile runs COPY . . and there's no .dockerignore. Is the key in the image?