LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 613 min read

Faster, Cleaner Builds: Cache, .dockerignore and Tags

Order Dockerfile steps to use the build cache, keep unwanted files out with .dockerignore, tag image versions, and understand CMD vs ENTRYPOINT.

On this page 10 sections

Introduction

You will rebuild images many times a day, so build speed matters. A few small habits make rebuilds take seconds instead of minutes, and keep your images clean and properly versioned.

What You Will Learn
  • How Docker reuses unchanged layers, and what breaks the cache.
  • How to order Dockerfile steps so dependencies are not reinstalled on every code change.
  • How .dockerignore keeps secrets and junk out of the build.
  • How to tag versions, and the difference between CMD and ENTRYPOINT.

How the Build Cache Works

Each Dockerfile instruction produces a layer. When you rebuild, Docker checks each step: if the instruction and the files it uses are unchanged, it reuses the stored layer and prints CACHED. As soon as one step changes, that step and every step after it run again.

docker build -t quotes-api . # first build: every step runs
docker build -t quotes-api . # nothing changed: every step is CACHED, done in about a second

With the Dockerfile from lesson 5, the problem shows up when you change one line of app.py: COPY . . sees a changed file, so it runs again — and so does RUN pip install, which comes after it. Every small code change reinstalls all the dependencies.

Order Your Dockerfile for the Cache

The fix is to copy the dependency list on its own first, install dependencies, and copy the rest of the code afterwards:

Dockerfile
FROM python:3.14-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

Now a code change only invalidates the second COPY. The pip install layer stays CACHED unless requirements.txt itself changes. The rule: put things that change rarely near the top, and things that change often near the bottom.

LanguageCopy firstThen install with
Pythonrequirements.txtpip install -r requirements.txt
Node.jspackage.json and package-lock.jsonnpm ci
Java (Maven)pom.xmlmvn dependency:go-offline
Gogo.mod and go.sumgo mod download

To ignore the cache completely — for example to pick up the latest security updates of packages — build with docker build --no-cache -t quotes-api .. Add --pull to also check for a newer base image.

.dockerignore

docker build sends the whole build context folder to Docker before it starts. A virtual environment, a .git folder or large data files make every build slower — and COPY . . would copy them, and any secrets such as .env files, into the image. A .dockerignore file next to the Dockerfile lists what to leave out, using the same patterns as .gitignore:

.dockerignore
.git
.venv
__pycache__/
*.pyc
.env
Dockerfile
.dockerignore

Watch the transferring context line in the build output: with a large .venv or .git folder it can be tens of megabytes; with a good .dockerignore it drops to a few kilobytes.

Mini Challenge · Spot the Problem

Which Dockerfile ordering makes every code change reinstall all dependencies?

Choose one answer

Tagging Versions

So far every build overwrote quotes-api:latest. Give each release its own version tag — you can apply several tags in one build:

docker build -t quotes-api:1.1 -t quotes-api:latest .
docker images quotes-api

Both tags point to the same image ID. When you build 1.2 later, 1.1 stays on your machine, so you can always go back to it. An older image that loses its last tag shows up as <none> — a “dangling” image that docker image prune removes.

CMD vs ENTRYPOINT

Both say what a container runs, but they behave differently when you add arguments to docker run. Anything after the image name replaces CMD completely:

docker run --rm quotes-api python --version # runs python --version instead of app.py

ENTRYPOINT is fixed, and CMD becomes its default arguments. With this Dockerfile:

Dockerfile
FROM alpine
ENTRYPOINT ["echo", "Hello,"]
CMD ["world"]
docker build -t greet .
docker run --rm greet # prints: Hello, world
docker run --rm greet Priya # prints: Hello, Priya
CMDENTRYPOINT
Arguments after the image nameReplace itAre appended to it
Typical useApps and services: the usual start commandImages that behave like a single command-line tool
Override withdocker run IMAGE other-commanddocker run --entrypoint other IMAGE

Common Mistakes

Avoid These Mistakes
  • COPY . . before installing dependencies, making every build slow.
  • No .dockerignore, so .env files and .git history end up inside images.
  • Overwriting latest on every build and losing the ability to roll back.
  • Combining unrelated commands in one RUN line only to save layers, at the cost of caching — split by how often things change.

Frequently Asked Questions

docker builder prune removes unused build cache. docker system df shows how much space it uses.

RUN apt-get update && apt-get install -y curl in one step guarantees the package list is fresh whenever the install step runs. As separate steps, a cached, outdated update layer could be reused.

Use docker build --progress=plain -t myapp . to print every line from every step.

Key Takeaways

  • Unchanged steps are CACHED; a change re-runs that step and everything after it.
  • Copy dependency files and install them before copying the rest of the code.
  • .dockerignore speeds up builds and keeps secrets and junk out of images.
  • Tag each release with a version (and optionally latest).
  • Arguments to docker run replace CMD but are appended to ENTRYPOINT.

Summary

Your builds are now fast and your images clean and versioned. Next you will connect containers to the outside world and to each other with ports and networks.

Next Lesson →

Ports and Networks: Connecting Containers