On this page
The core idea
A Docker image is built in layers, one per instruction. Docker reuses a cached layer when the instruction and its inputs have not changed, but once one layer changes, every layer after it is rebuilt. Copying the whole project before installing dependencies means any code edit invalidates the dependency layer. Copying only the dependency files first, installing, and then copying the source keeps the slow step cached. Multi-stage builds go further: build tools stay in an earlier stage and only the runtime output is copied into the final image.
A practical approach
Step 1
Start from a specific, maintained base image tag rather than latest, and prefer a slim variant suitable for your runtime.
Step 2
Copy dependency manifests such as package.json with its lock file, or requirements.txt, then run the install step before copying the rest of the source.
Step 3
Add a .dockerignore that excludes .git, local environments, node_modules, build output and secrets so they are never sent to the build context.
Step 4
Use a multi-stage build: compile or bundle in a builder stage, then copy only the built artefacts and production dependencies into a slim runtime stage.
Step 5
Run the application as a non-root user and define a clear CMD, so the container starts the same way everywhere.
Worked example
A FastAPI service took four minutes to rebuild after every one-line change because the Dockerfile ran COPY . . followed by pip install -r requirements.txt. Moving COPY requirements.txt . and the install above COPY . . reduced most rebuilds to a few seconds. Adding a .dockerignore that excluded the local .venv and test data also shrank the build context sent to Docker.
A mistake to avoid
Do not copy secrets such as .env files or credentials into an image, even in an early stage. Anything in a layer can be recovered from the image. Provide secrets at runtime through environment variables or your platform's secret store.
Try it yourself
Take an existing Dockerfile, time a rebuild after a one-line code change, then reorder it so dependencies install before the source is copied. Add a .dockerignore and compare build time and image size before and after.
Your implementation checklist
- Dependency files are copied and installed before the source
- A .dockerignore excludes unneeded and sensitive files
- The base image has a pinned, maintained tag
- A multi-stage build keeps build tools out of the final image
- The container runs as a non-root user
Share or cite this guide
Innovate with Ajay Editorial (2026-10-07). Write a Dockerfile That Caches Dependencies and Ships Less. https://innovatewithajay.com/blogs/write-a-dockerfile-that-caches-dependencies
