If you’re still using pip
in your Docker containers, you’re probably missing out on some serious performance gains. I recently switched all my Python projects to use uv instead of pip, and the difference is night and day.
What is uv?
uv is a Python package manager written in Rust that’s designed to be a drop-in replacement for pip, but way faster. Think of it as what pip should have been if it was built today.
The Old Way: pip in Docker
Here’s what most Python Dockerfiles look like:
1FROM python:3.12-slim
2WORKDIR /app
3COPY requirements.txt .
4RUN pip install -r requirements.txt
5COPY . .
6CMD ["python", "app.py"]
This works, but it’s slow and doesn’t take advantage of modern Docker features like build mounts and caching.
The New Way: uv in Docker
With uv, you get much better performance and built-in features that work great with Docker. Here’s how to set it up:
Installing uv in Docker
The easiest way is to copy the binary from the official uv image:
1FROM python:3.12-slim
2COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
You can also use their pre-built images that come with uv already installed:
1FROM ghcr.io/astral-sh/uv:python3.12-slim
Basic uv Dockerfile
Here’s a simple Dockerfile using uv:
1FROM python:3.12-slim
2COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
3
4WORKDIR /app
5
6# Copy dependency files
7COPY pyproject.toml uv.lock ./
8
9# Install dependencies
10RUN uv sync --locked
11
12# Copy source code
13COPY . .
14
15CMD ["uv", "run", "python", "app.py"]
Optimized Dockerfile with Caching
But here’s where uv really shines - with proper caching and layer optimization:
1FROM python:3.12-slim
2COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
3
4WORKDIR /app
5
6# Install dependencies in a separate layer
7RUN --mount=type=cache,target=/root/.cache/uv \
8 --mount=type=bind,source=uv.lock,target=uv.lock \
9 --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
10 uv sync --locked --no-install-project
11
12# Copy the project and install it
13COPY . .
14RUN --mount=type=cache,target=/root/.cache/uv \
15 uv sync --locked
16
17CMD ["uv", "run", "python", "app.py"]
The --no-install-project
flag installs your dependencies but not your actual project code. Since dependencies change way less often than your code, this creates a much better cache layer.
Why uv is Better for Docker
Speed
uv is written in Rust and is significantly faster than pip. We’re talking 10-100x faster in many cases.
Better Caching
uv has built-in cache management that works perfectly with Docker’s build cache. The --mount=type=cache
directive keeps your downloaded packages between builds.
Lock Files
uv generates proper lock files (uv.lock
) that pin exact versions of all dependencies, including transitive ones. This means your builds are actually reproducible.
Project Management
If you’re using pyproject.toml
(which you should be), uv handles it natively without needing additional tools.
Migration Tips
From requirements.txt
If you’re currently using requirements.txt
, you can easily migrate:
1# Generate uv.lock from requirements.txt
2uv add $(cat requirements.txt)
Using uv with System Python
If you want to install packages in the system Python environment (which is fine in containers), use the --system
flag:
1ENV UV_SYSTEM_PYTHON=1
2RUN uv pip install package-name
Multi-stage Builds
For production containers, you can use multi-stage builds with --no-editable
to copy just the environment without source code:
1# Build stage
2FROM python:3.12-slim AS builder
3COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
4WORKDIR /app
5RUN --mount=type=cache,target=/root/.cache/uv \
6 --mount=type=bind,source=uv.lock,target=uv.lock \
7 --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
8 uv sync --locked --no-install-project --no-editable
9COPY . .
10RUN --mount=type=cache,target=/root/.cache/uv \
11 uv sync --locked --no-editable
12
13# Runtime stage
14FROM python:3.12-slim
15COPY --from=builder /app/.venv /app/.venv
16CMD ["/app/.venv/bin/python", "app.py"]
Real-World Results
In my projects, I’ve seen:
- Build times reduced by 60-80% thanks to better caching
- Smaller images with multi-stage builds
- More reliable builds with proper lock files
- Simpler Dockerfiles with fewer RUN commands
Getting Started
The migration is pretty straightforward:
- Add
uv
to your Dockerfile - Replace
pip install
withuv sync
oruv pip install
- Use build mounts for caching
- Consider using lock files for reproducible builds
Check out the official uv Docker documentation for more detailed examples and best practices.
Trust me, once you try uv in Docker, you won’t want to go back to pip. The performance improvement alone is worth the switch.