Cut Rebuild Time With a BuildKit Cache Mount
Add a BuildKit cache mount to pip install so a changed requirements file downloads only the new packages. After this, a dependency change no longer means downloading everything again.
Every time requirements.txt changes, pip downloads every package again, including the big ones that did not change. A BuildKit cache mount keeps pip's download cache between builds without putting it in the image. This page is the short version of the BuildKit lesson in the Docker Advanced course. I ran it on Docker Desktop 4.85.0 (Engine 29.6.2) with python:3.14-alpine, on a fresh builder so the first build starts with an empty cache.
Before you start
Check that your Dockerfile already copies the dependency list before the rest of the code. If COPY . . comes before pip install, fix that first. A cache mount does not help with code edits; the layer order does.
The Dockerfile
numpy==2.5.4
import numpy
print("numpy", numpy.__version__)
FROM python:3.14-alpine
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --root-user-action=ignore -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]
The only new part is --mount=type=cache,target=/root/.cache/pip on the RUN line. /root/.cache/pip is where pip keeps downloads when it runs as root. Do not add --no-cache-dir to that pip install; it would tell pip to ignore the cache you just mounted.
First build
Set-Location mount-demo
$t = Measure-Command { $log = docker build --progress=plain -t mount-demo . 2>&1 | ForEach-Object { "$_" } }
$log | Select-String "Downloading numpy|Using cached numpy" | ForEach-Object { $_.Line -replace '^#\d+ \d+\.\d+ +', '' }
"build took {0:N0} s" -f $t.TotalSeconds
docker run --rm mount-demo
Downloading numpy-2.5.4-cp314-cp314-musllinux_1_2_x86_64.whl.metadata (6.6 kB)
Downloading numpy-2.5.4-cp314-cp314-musllinux_1_2_x86_64.whl (18.5 MB)
build took 22 s
numpy 2.5.4
Change a dependency and build again
Add a second package. The install step has to run again, because its input changed.
Add-Content requirements.txt "flask==3.1.3"
$t = Measure-Command { $log = docker build --progress=plain -t mount-demo . 2>&1 | ForEach-Object { "$_" } }
$log | Select-String "Downloading numpy|Using cached numpy" | ForEach-Object { $_.Line -replace '^#\d+ \d+\.\d+ +', '' }
"build took {0:N0} s" -f $t.TotalSeconds
Using cached numpy-2.5.4-cp314-cp314-musllinux_1_2_x86_64.whl.metadata (6.6 kB)
Using cached numpy-2.5.4-cp314-cp314-musllinux_1_2_x86_64.whl (18.5 MB)
build took 15 s
pip took numpy, an 18 MB download, from the cache and downloaded only Flask and its dependencies. The times will be different on your machine and your connection. What matters is the Using cached line.
Check the cache is not in the image
docker run --rm mount-demo sh -c "ls /root/.cache/pip 2>&1 || true"
No such file or directory
The cache lives in the builder, not in the image. Pushing the image does not push the cache.
Other package managers
Use the cache folder of your tool as the target:
| Tool | target |
|---|---|
| pip (root) | /root/.cache/pip |
| npm | /root/.npm |
| Go modules and build cache | /go/pkg/mod and /root/.cache/go-build |
| apk | /var/cache/apk (add sharing=locked) |
I only ran the pip version.
If it does not work
- Every build still downloads everything: you are probably on a fresh CI runner or a new builder, which starts empty. The cache is per builder.
Using cachednever shows up: check that pip does not run as another user withUSERbefore the install. A non-root user keeps its cache in its own home folder, so the target must match.- The image is missing packages: you installed into the cache folder instead of from it. Nothing in a cache mount ends up in the image.
💡 Tip: To empty the build cache of the default builder, docker builder prune removes cache that nothing uses. Read what it says before you confirm, because it is not limited to one project.
Related course: Docker Advanced.
About
Add a BuildKit cache mount to pip install so a changed requirements file downloads only the new packages. After this, a dependency change no longer means downloading everything again.
- Responsible
- Jay P
- Last Update
- 08/01/2026
- Reading Time
- 4 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.