2026, Jan 11 01:00

Fix slow Docker Python builds: point pip cache to $HOME/.cache/pip or set --cache-dir explicitly

Learn why pip cache mounts in Docker miss when HOME changes, and how to fix them by targeting $HOME/.cache/pip or using --cache-dir to speed up Python builds.

Docker builds that install Python dependencies can feel painfully slow. A common optimization is to cache downloads so pip doesn’t fetch the same wheels over and over. If you’ve added a cache mount and it still looks like nothing changed, the reason is often much simpler than it seems: the cache directory doesn’t match where pip actually stores its cache.

Reproducing the slowdown

The setup below tries to cache packages between builds, yet every build takes the same amount of time.

FROM python:3.13-slim AS build-base
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir --upgrade pip
FROM python:3.13-slim AS app-image
ENV PATH="/opt/venv/bin:$PATH"
COPY --from=build-base /opt/venv /opt/venv
WORKDIR /app
ENV HOME=/app
COPY . .
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
    pip install -e .

What’s really happening

pip places its cache under $HOME/.cache/pip. In this Dockerfile, the environment sets HOME=/app. That means the effective cache path is /app/.cache/pip, not /root/.cache/pip. With the mount pointing at /root/.cache/pip, pip is simply not seeing the cache, so it re-downloads content on every build.

There’s another subtlety when judging performance. Even when the content cache is used, the Dockerfile step that runs pip install is executed again during rebuilds. pip can reuse the downloaded wheel, but it still unpacks and installs it, which takes visible time. To verify reuse rather than guess, build with plain progress and read the logs:

docker build --progress=plain .

If you see messages indicating that a downloaded wheel is being used, the cache is working. If you see warnings like a cache directory not being writable under /app/.cache/pip, that’s another indicator the paths don’t align with $HOME.

The fix

Align the cache mount with the actual home directory. If you explicitly set HOME to /app, mount the cache there:

RUN --mount=type=cache,target=/app/.cache/pip \
    pip install -e .

In many containers the notion of a home directory doesn’t matter. If you don’t set HOME and you’re running as root, it defaults to /root, so mounting at /root/.cache/pip works as intended:

# no need to override HOME; root's home is /root
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -e .

You can also steer pip to a specific cache path explicitly. This is handy if you want to be unambiguous about where the cache lives:RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \ pip install --cache-dir=/root/.cache/pip -e .

You can combine cache mounts with Docker’s regular layer caching strategy. Running dependency installation in an earlier stage and structuring the build so that only the dependency metadata (for example, pyproject.toml) is copied before the install step helps avoid re-running installation when only application source changes, while still benefiting from the content cache.

Why this matters

Correctly configured caching eliminates redundant downloads, reduces bandwidth usage, and speeds up iterative builds. Misaligned paths waste these benefits and can mask the fact that the cache is never touched. Understanding that pip ties its cache to $HOME is the key to making cache mounts pay off.

Practical takeaways

Point the cache mount where pip actually looks, which is $HOME/.cache/pip. If you set HOME=/app, mount /app/.cache/pip. If you don’t set HOME and run as root, mount /root/.cache/pip. When in doubt, pass --cache-dir to pip to be explicit. And when evaluating whether caching works, inspect the build logs with plain progress; pip may still spend some time unpacking cached wheels, but it should not be downloading them again.