TT Lab
Get started
Learn Learning paths Courses

Building Images

It Is Already Slow Before the Build Starts

Continue in TT Lab

One-line summary

That single dot in docker build . sends the whole current directory to the builder. No matter how well you design the cache, if there are 800 MB to transfer, the build is already slow before it even starts.

Why this is needed

The Dockerfile has only two lines, yet the build takes 40 seconds. The answer is in the first line of the log.

Sending build context to Docker daemon  812.4MB

Before the build even starts, this is the step that compresses the whole current directory and sends it to the builder. node_modules, .git, dist and even the dump file you downloaded yesterday all go. Even if the Dockerfile does not use them, they are transferred.

Two things break at once here.

  1. Speed — hundreds of MB are moved on every build.
  2. Cache — COPY . . breaks if even one file in the context changes. .git/index changes even if you just run git status. So the cache can break even when you have not edited the source.

How it works

.dockerignore has a syntax similar to .gitignore but a different purpose. .gitignore means "what not to put under version control", and .dockerignore means "what not to send to the builder". They overlap a lot but are not the same — for example, .git cannot be put in .gitignore but must be put in .dockerignore.

.git
node_modules
**/__pycache__
*.log
dist/
.env

The last line matters. If you leave .env in the context, COPY . . bakes it into the image as it is. Even if you add a layer that deletes it later, it remains in the earlier layer.

Reproducibility — the same Dockerfile, a different result

There are two more common reasons a build is not reproducible even after you shrink the context with .dockerignore.

Floating tags. FROM python:3.12 is a different image today than next month. Pin it like FROM python:3.12.7-slim, or, more strictly, use the digest with FROM python@sha256:....

Package index. apt-get install curl fetches the latest version of that day. This is the classic cause of a build that passed yesterday breaking today, and where it is really needed, specify the version.

Problem Symptom Response
Large context "Sending build context" is hundreds of MB .dockerignore
.git included The cache breaks even though the source was not edited In .dockerignore, add .git
Floating tag The result differs from last week's build Pin the patch version or digest
Secret inflow .env ends up in the image .dockerignore + build secret

ARG remains in the image

ARG API_TOKEN
RUN curl -H "Authorization: $API_TOKEN" ...

If you write it like this, the value remains in docker history. Pass build-time secrets not through ARG but through BuildKit's secret mount. Use ARG only for values that are fine to make public, such as a "version number".

What it looks like in the field

Ways not to send it, other than shrinking the context

.dockerignore reduces what is sent, but there are also ways to bring things in by a completely different route. Which one is right depends on why that file is needed for the build.

What is needed only for the build and must not be in the final image — the multi-stage build and secret mounts you saw in the previous course fit here. Credentials are not put in the context at all but handed in through a mount, and compile tools and intermediate outputs are left in an earlier stage with only the results moved over.

What is fetched during the build — if you use a cache mount, you can reuse the dependency directory between builds without putting it in the context. This is why you do not have to send node_modules wholesale, and the context gets smaller while the cache gets better at the same time.

What is fetched from a remote — instead of a context, the builder can also take a git repository address or a tarball address. In an environment like CI where the source is already in place, it makes little difference, but in a place where you cannot create a context, this route is the only one.

It is also good to know how to check what was actually sent. What goes into the context is easy to get confused about depending on the order of .dockerignore rules and negation patterns, but if you build the image and skim the file list inside it, you immediately see whether anything unintended went in. This is also the surest way to check whether a secret went in, and as you saw in the previous course, it cannot be undone once it is in a registry, so one look before pushing is worth a lot.

What to look for in the next check

In the quiz that follows, you will judge the boundary of the build context, .dockerignore, cache keys, and how secrets are passed. Based on the transfer size and cache invalidation results you measured in the previous lab, distinguish the effect that files unrelated to source changes have on the build.