Build images

Control the build context

Keep secrets, dependencies, Git history, and unrelated files out of image builds with a small context and .dockerignore.

Remember the final . in docker build -t notes-api:dev .? That’s the build context: the directory Docker sends to the builder before running your Dockerfile. COPY can only read files from there. A file outside the context doesn’t exist as far as the build is concerned.

That sounds like a detail. It’s not, for two reasons.

Speed

Docker sends the whole context to the builder, every build. If your project folder has a node_modules with 300 MB of dependencies and a .git folder with years of history, each build starts by copying all that. You’ll see it in the output, in a line like transferring context: 312.4MB.

Safety

The second reason is worse. Anything in the context is available to COPY. A COPY . . copies your .env file with real credentials into the image. And here’s the trap: deleting it in a later RUN step doesn’t help. Each instruction is a layer, and the file is still in the earlier layer. Anyone with the image can dig it out.

So never copy a secret and then try to clean it up. Keep it out of the context in the first place.

.dockerignore

A .dockerignore file lists paths to exclude from the context. It works like .gitignore. Create it next to the Dockerfile:

node_modules
.git
.env
coverage
dist
*.log

Excluded files never reach the builder, so COPY can’t grab them by accident. node_modules is excluded because we’ll install dependencies inside the image, where they match the image’s operating system. dist is excluded for the same reason: we build inside the image.

See the context size

Run a build with plain progress output and read the context line:

docker build --progress=plain -t notes-api:dev .

Look for transferring context. For the Notes API it should be a few kilobytes.

If it’s unexpectedly large, check two things before anything else: the .dockerignore file, and the directory you passed as the last argument. Running docker build from your home folder with . as the context sends your entire home folder. It happens.

When the build really needs a secret

Sometimes an install step needs a token, for example to download a private npm package. Don’t copy it. Use a BuildKit secret mount, which makes the file available only to that one RUN step and never writes it into a layer. In the Dockerfile:

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

And at build time:

docker build --secret id=npmrc,src=$HOME/.npmrc -t notes-api:dev .

Try this: create .dockerignore, then add a temporary 100 MB file to the project with head -c 100000000 /dev/zero > big.bin. Build once with big.bin in .dockerignore and once without, and compare the context size in the output.

Lesson completed