Why builds are fast the second time
Running docker build twice in a row finishes the second run instantly, with CACHED next to every step. Docker reuses the layer from last time whenever it can prove nothing changed.
The cache rule is worth memorizing.
- An instruction's layer is reused if the instruction text is unchanged and any files it copies are unchanged.
- The moment one step misses the cache, every step after it rebuilds too, because each layer builds on the previous one.
| Change | First step to miss |
|---|---|
edit server.py | the COPY . . near the bottom |
edit requirements.txt | the copy of that file, near the top |
edit the FROM line | everything |
Rule 2 is why order matters. Put the things that change rarely, meaning dependencies, near the top, and the things that change constantly, meaning your code, near the bottom.
The classic pattern, explained
The Dockerfile from lesson 3-1 now makes full sense:
COPY requirements.txt . RUN pip install -r requirements.txt COPY . .
Copying requirements.txt alone first and everything later is a deliberate choice driven by what changes between builds.
Editing server.py and rebuilding leaves requirements.txt unchanged, so its copy and the whole pip install layer are cache hits, and only the final COPY . . reruns. Build time is about a second.
If the Dockerfile instead said COPY . . before pip install, any code edit would invalidate that copy, and rule 2 would force pip to reinstall every package on every build. Build time becomes minutes, every time.
| Ordering | Rebuild after a code edit |
|---|---|
| dependencies first | one cheap copy layer |
| code first | a full reinstall |
The resulting image is identical either way. The difference is entirely in the feedback loop, which is what determines whether developers rebuild freely or avoid it.
A cache oracle in bash
A small script playing the role of Docker deciding where a rebuild starts, based on which file changed.
changed="server.py" case "$changed" in requirements.txt) echo "first miss: COPY requirements.txt (pip install reruns too)" ;; *) echo "first miss: COPY . . (pip install layer reused)" ;; esac
Output
first miss: COPY . . (pip install layer reused)
server.py changed here, so the expensive pip install layer is safely reused. Setting changed to requirements.txt takes the other branch, where the install reruns because a layer above it missed.
The case form is bash's pattern match, with *) as the catch-all and ;; ending each branch. It models the real cache decision closely, because Docker also stops at the first miss and rebuilds everything below it.
Which steps rerun after a code edit
For a Dockerfile of FROM, COPY requirements.txt, RUN pip install, COPY ., and CMD, editing only server.py reruns just COPY . . and the cheap CMD metadata step after it.
requirements.txt did not change, so its copy layer is a cache hit and so is the pip install layer built on top of it. The first miss is COPY . ., and everything after that point reruns.
| Step | Status |
|---|---|
FROM | cached |
COPY requirements.txt | cached |
RUN pip install | cached |
COPY . . | rebuilt |
CMD | rebuilt, and free |
This is exactly why dependencies are copied and installed before the code. The expensive step sits above the line where changes happen, so it is almost never invalidated.
.dockerignore
COPY . . copies the whole build context, which can drag in junk: the .git history, node_modules/, virtualenvs, and .env files holding secrets. That bloats the image, slows the build, and can leak credentials.
The fix is a .dockerignore file next to your Dockerfile, the same idea as the .gitignore from the git course:
.git node_modules __pycache__ .env *.log
| Entry | Keeps out |
|---|---|
.git | the entire version history |
node_modules | reinstallable dependencies |
.env | secrets |
*.log | noise, matched by pattern |
Anything listed is excluded from the build context, so COPY . . never sees it. Every real project should have one, and .env belongs in it for exactly the reason it belongs in .gitignore. Secrets get a proper home in lesson 8-2.
The file that trims the build context
The file is .dockerignore.
It works like .gitignore but for docker build, and the name starts with a dot and ends with ignore. It keeps .git, dependency folders, caches, and secret files such as .env out of the build context, so COPY cannot pull them into the image.
| Benefit | Why |
|---|---|
| smaller images | fewer bytes in the copy layer |
| faster builds | less context to send to the daemon |
| no leaked secrets | .env never reaches the image |
The last benefit is the one people underestimate. A secret baked into a layer stays in the image history even if a later instruction deletes the file, so keeping it out of the context in the first place is the only real fix.