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.
- 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 runsdocker build -t quotes-api . # nothing changed: every step is CACHED, done in about a secondWith 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:
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.
| Language | Copy first | Then install with |
|---|---|---|
| Python | requirements.txt | pip install -r requirements.txt |
| Node.js | package.json and package-lock.json | npm ci |
| Java (Maven) | pom.xml | mvn dependency:go-offline |
| Go | go.mod and go.sum | go 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:
.git.venv__pycache__/*.pyc.envDockerfile.dockerignoreWatch 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.
Which Dockerfile ordering makes every code change reinstall all dependencies?
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-apiBoth 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.pyENTRYPOINT is fixed, and CMD becomes its default arguments. With this Dockerfile:
FROM alpineENTRYPOINT ["echo", "Hello,"]CMD ["world"]docker build -t greet .docker run --rm greet # prints: Hello, worlddocker run --rm greet Priya # prints: Hello, Priya| CMD | ENTRYPOINT | |
|---|---|---|
| Arguments after the image name | Replace it | Are appended to it |
| Typical use | Apps and services: the usual start command | Images that behave like a single command-line tool |
| Override with | docker run IMAGE other-command | docker run --entrypoint other IMAGE |
Common 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.