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:

  1. Add uv to your Dockerfile
  2. Replace pip install with uv sync or uv pip install
  3. Use build mounts for caching
  4. 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.