Originally published on docker.learnio.dev — part of the Docker fundamentals course. View this lesson on the original site.
Everything in the directory rides along with docker build
The final argument to docker build is the build context path. Docker sends that directory tree to the builder before any instruction runs. .dockerignore is the coarse filter that keeps known junk, local state, and credentials out of that transfer.
Check what the context contains
Before editing the Dockerfile, see what the builder would receive:
du -sh ./* ./.[!.]* 2>/dev/null | sort -h
docker build --progress=plain -t demo:context .
If transferring context reports tens or hundreds of megabytes for a small app, start with .dockerignore.
Create and validate .dockerignore
Place .dockerignore next to the Dockerfile. Patterns follow .gitignore syntax: one pattern per line, ! to re-include, and trailing / for directories.
Example for a typical web service repository:
.git
.gitignore
.dockerignore
.idea
.vscode
node_modules/
vendor/
dist/
build/
coverage/
*.log
.env
.env.*
!.env.example
*.pem
*.key
id_rsa*
tmp/
.cache/
Build again and confirm the context shrank:
docker build --progress=plain -t demo:ignore .
You can also sanity-check ignored paths without building:
git check-ignore -v node_modules .env .git 2>/dev/null || true
Adjust the list for your stack. The goal is to ship source and build inputs, not your whole working directory.
What still belongs in the context
Include files the Dockerfile actually needs: source code, dependency manifests, static assets referenced by COPY, and small build scripts. Exclude generated output you rebuild inside the image, local secrets, and VCS metadata unless a step genuinely requires it.
Keep secrets out of layers
Build args and environment variables are the wrong shape for secrets. They can persist in image history or config. Prefer runtime injection for production credentials, and build-time mounts for values needed during one step.
Runtime pattern (secret supplied when the container starts, not at build time):
docker run --rm -e DATABASE_URL="$DATABASE_URL" myorg/api:latest
Build secret mount (available only for that RUN line):
# syntax=docker/dockerfile:1.7
RUN --mount=type=secret,id=registry_token,target=/run/secrets/token \
npm ci --registry=https://registry.example.com/ \
--//registry.example.com/:_authToken="$(cat /run/secrets/token)"
Pass the secret at build time:
echo "$NPM_TOKEN" | docker build --secret id=registry_token,src=/dev/stdin -t myorg/api .
Private Git over SSH during build:
RUN --mount=type=ssh \
git clone [email protected]:myorg/private-lib.git /opt/private-lib
docker build --ssh default -t myorg/api .
The secret or key is available to that step only. It is not copied into a normal filesystem layer the way COPY credentials.json . would be.
Context hygiene and secret hygiene are one habit
.dockerignore decides what never reaches the builder. Secret mounts decide what may appear briefly during a single instruction. Both choices keep artifacts smaller, builds faster, and credentials out of registries.
GitHub example
Academy repo: 2-images-layers/dockerignore-context
From Docker fundamentals — Images, layers, tags, and everyday commands