Mastering UV with Python and Docker: A Comprehensive Guide to Modern Python Development

Prerequisites

  • Intermediate understanding of Python development (Python 3.13 recommended)
  • Basic familiarity with Docker concepts
  • Docker and Docker BuildX installed (Docker version 20.10+)
  • Minimum 4GB RAM and 10GB disk space

Introduction

UV is a blazing-fast Python package manager written in Rust that’s revolutionizing Python application development. By integrating UV with Docker, you can create efficient, reproducible Python environments that are portable and optimized for both development and production.

In this guide, we’ll explore two primary approaches:

We’ll discuss the pros and cons of each method, best practices, and when to use them. Whether you’re building a simple application or deploying a complex, multi-architecture system, this guide has you covered.

What We’re Building

We’ll create:


Table of Contents

  1. Prerequisites
  2. Two Approaches to Using UV in Docker
  3. When to Use Each Approach
  4. Getting Started: Basic UV Setup
  5. Development Environment Setup
  6. Production Environment Setup
  7. Best Practices
  8. Advanced Topics
  9. Performance Comparison
  10. Real-World Example: FastAPI Application
  11. Setting Up Multi-Architecture Builds
  12. Managing Dependencies
  13. Working with Private Packages
  14. Troubleshooting Guide
  15. Example Repository: uv-docker-starter
  16. Conclusion

Prerequisites

Before diving in, make sure you have the following:

Check your versions:

docker --version       # Should be 20.10 or higher
docker buildx version  # Should show BuildX version
python --version       # Should be Python 3.13.x

Two Approaches to Using UV in Docker

Approach 1: Using Official UV Docker Images

The simplest way to get started with UV in Docker is by using the official UV Docker images.

Dockerfile Example:

FROM ghcr.io/astral-sh/uv:python3.13-bookworm
COPY . .
RUN uv pip install -r requirements.txt

Pros:

Cons:

Approach 2: Custom UV Integration

For more control and optimization, you can integrate UV into your own Docker images.

Dockerfile Example:

# syntax=docker/dockerfile:1.4

# Build stage
FROM python:3.13-slim-bookworm AS builder

# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
ENV UV_SYSTEM_PYTHON=1

WORKDIR /build

# Install dependencies
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install --system --compile-bytecode \
    --no-editable --only-binary :all: \
    -r pyproject.toml

# Final stage
FROM python:3.13-slim-bookworm

WORKDIR /app
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
COPY . .

# Run as non-root user
RUN useradd -m -s /bin/bash appuser
USER appuser

Pros:

Cons:


When to Use Each Approach

Use Official UV Docker Images When:

  1. Building simple applications
  2. Creating proof-of-concept projects
  3. Learning UV and Docker
  4. Quick prototyping
  5. CI/CD testing environments

Use Custom UV Integration When:

  1. Building production applications
  2. Optimizing for size and performance
  3. Implementing complex deployment strategies
  4. Requiring multi-architecture support
  5. Managing multiple environments (development/production)

Getting Started: Basic UV Setup

Using Official UV Docker Images

This is the simplest setup using the official UV image.

Dockerfile:

FROM ghcr.io/astral-sh/uv:python3.13-bookworm
COPY . .
RUN uv pip install -r requirements.txt

CMD ["python", "-m", "your_application"]

Custom UV Integration

For more control and optimization, start with a base Python image and integrate UV.

Dockerfile:

# syntax=docker/dockerfile:1.4

FROM python:3.13-slim-bookworm AS base

# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
ENV UV_SYSTEM_PYTHON=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

Development Environment Setup

Official Image Approach

docker-compose.yml:

services:
  app:
    image: ghcr.io/astral-sh/uv:python3.13-bookworm
    volumes:
      - .:/app
    command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
    ports:
      - "8000:8000"

Custom Integration Approach

docker-compose.yml:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
      - uv-cache:/root/.cache/uv
    environment:
      - UV_SYSTEM_PYTHON=1
      - PYTHONUNBUFFERED=1
    command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
    ports:
      - "8000:8000"

volumes:
  uv-cache:

Dockerfile.dev:

# syntax=docker/dockerfile:1.4

FROM python:3.13-slim-bookworm

# Install development dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
ENV UV_SYSTEM_PYTHON=1 \
    UV_LINK_MODE=copy \
    PYTHONUNBUFFERED=1

WORKDIR /app

Hot Reloading and Debugging

To enable hot reloading and debugging, consider integrating tools like watchdog or debugpy.

Dockerfile.dev (Additions):

RUN uv pip install --system watchdog debugpy

docker-compose.yml (Additions):

environment:
  - DEBUG=True

Production Environment Setup

Official Image Approach

Dockerfile:

FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim

WORKDIR /app
COPY . .
RUN uv pip install --system -r requirements.txt

# Run as non-root user
RUN useradd -m -s /bin/bash appuser
USER appuser

CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]

Custom Integration Approach

Dockerfile:

# syntax=docker/dockerfile:1.4

# Build stage
FROM python:3.13-slim-bookworm AS builder

# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
ENV UV_SYSTEM_PYTHON=1

WORKDIR /build
COPY pyproject.toml uv.lock ./

# Install dependencies with caching
RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install --system --compile-bytecode \
    --no-editable --only-binary :all: \
    -r pyproject.toml

# Final stage
FROM python:3.13-slim-bookworm

WORKDIR /app

# Copy installed packages
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages

# Copy application code
COPY . .

# Run as non-root user
RUN useradd -m -s /bin/bash appuser
USER appuser

# Expose port and set entrypoint
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]

Best Practices

Security

Caching

Use Docker BuildKit cache mounts to speed up dependency installation.

RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install [...]

Multi-Architecture Support

Leverage Docker BuildX for building images that support multiple architectures.

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp:latest \
  --push .

Optimization Techniques


Advanced Topics

Testing Framework Integration

Integrate testing tools like Pytest into your CI/CD pipeline to ensure code quality.

Dockerfile.test:

FROM python:3.13-slim-bookworm

# Install test dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
ENV UV_SYSTEM_PYTHON=1

WORKDIR /app
COPY . .

RUN uv pip install --system -r requirements.txt
RUN uv pip install --system pytest

CMD ["pytest"]

GitHub Actions Workflow Snippet:

- name: Run Tests
  run: docker build -f Dockerfile.test -t myapp-test . && docker run myapp-test

Monitoring and Logging

Set up performance monitoring and centralized logging.

Docker Compose Configuration:

services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"

Handling Edge Cases

Air-Gapped Environments

Set up a local Docker registry to mirror images and dependencies.

docker run -d -p 5000:5000 --restart=always --name registry registry:2

Update Docker daemon to use the local registry.

System Dependencies

Handle shared libraries and system packages.

RUN apt-get update && apt-get install -y libpq-dev

Performance Comparison

Build Time (Example Project)

ApproachFirst Build TimeSubsequent Builds (with Cache)
Official Image~2 minutes~2 minutes
Custom Integration~3 minutes~30 seconds

Image Size (Example Project)

ApproachImage Size
Official Image~1.2GB
Custom Integration~400MB

Memory Usage at Runtime (Example Project)

ApproachMemory Usage
Official Image~500MB
Custom Integration~300MB

Real-World Example: FastAPI Application

Here’s a complete example using FastAPI with custom UV integration.

Dockerfile:

# syntax=docker/dockerfile:1.4

# Build stage
FROM python:3.13-slim-bookworm AS builder

# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
ENV UV_SYSTEM_PYTHON=1

WORKDIR /build

# Copy dependency files
COPY pyproject.toml uv.lock ./

# Install dependencies with caching
RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install --system --compile-bytecode \
    --no-editable --only-binary :all: \
    -r pyproject.toml

# Final stage
FROM python:3.13-slim-bookworm

WORKDIR /app

# Copy installed packages
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages

# Copy application code
COPY . .

# Run as non-root user
RUN useradd -m -s /bin/bash appuser
USER appuser

# Expose port and set entrypoint
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]

Setting Up Multi-Architecture Builds

First, create and bootstrap a new BuildX builder.

# Create new builder instance
docker buildx create --name mybuilder --driver docker-container --bootstrap

# Use the new builder
docker buildx use mybuilder

Build your image for multiple architectures and push to a registry.

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t ghcr.io/username/myapp:latest \
  --push .

Managing Dependencies

Using requirements.txt

RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install --system -r requirements.txt

Using pyproject.toml

COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install --system -r pyproject.toml

Installing Specific Packages

RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install --system \
    fastapi~=0.109.0 \
    uvicorn~=0.27.0

Working with Private Packages

Using SSH Keys

Dockerfile:

# Assuming you have SSH keys set up
RUN --mount=type=ssh \
    uv pip install --system \
    git+ssh://git@github.com/org/repo.git

Building the Image:

Run the following command to build the image using the SSH key:

docker buildx build --ssh default -t myimage .

Using Access Tokens

When working with private repositories that require an access token, use Docker secrets to securely pass the token during the build process.

Dockerfile:

RUN --mount=type=secret,id=github_token \
    GITHUB_TOKEN=$(cat /run/secrets/github_token) && \
    uv pip install --system \
    git+https://${GITHUB_TOKEN}@github.com/org/repo.git

Explanation:

Building the Image:

docker buildx build --secret id=github_token,src=path_to_your_github_token_file -t myimage .

Alternative Approach Using requirements.txt

If you have multiple private packages, you can use a placeholder for the token in your requirements.txt file.

requirements.txt:

git+https://${GITHUB_TOKEN}@github.com/org/private-repo.git

Dockerfile:

COPY requirements.txt .
RUN --mount=type=secret,id=github_token \
    GITHUB_TOKEN=$(cat /run/secrets/github_token) && \
    sed "s/\${GITHUB_TOKEN}/${GITHUB_TOKEN}/g" requirements.txt > requirements_resolved.txt && \
    uv pip install --system -r requirements_resolved.txt && \
    rm requirements_resolved.txt

Building the Image:

docker buildx build --secret id=github_token,src=path_to_your_github_token_file -t myimage .

Key Notes:

  1. Replace path_to_your_github_token_file with the path to your token file.
  2. Ensure docker buildx is installed and enabled. If not, follow Docker Build Documentation for setup instructions.
  3. If BuildKit is not enabled by default, set DOCKER_BUILDKIT=1 as an environment variable.

This approach ensures secure and efficient builds leveraging BuildKit’s features.

Troubleshooting Guide

Common Issues

  1. UV Cache Permission Issues

    Error Message:

    PermissionError: [Errno 13] Permission denied: '/root/.cache/uv'
    

    Solution:

    Ensure the cache directory has the correct permissions.

    RUN mkdir -p /root/.cache/uv && chmod 777 /root/.cache/uv
    
  2. Platform-Specific Problems

    Issue:

    Missing platform-specific dependencies.

    Solution:

    Handle platform-specific dependencies using conditional statements.

    RUN apt-get update && \
        if [ "$(uname -m)" = "x86_64" ]; then \
            apt-get install -y package-amd64; \
        elif [ "$(uname -m)" = "aarch64" ]; then \
            apt-get install -y package-arm64; \
        fi
    
  3. Memory Issues

    Issue:

    Builds failing due to insufficient memory.

    Solution:

    Set resource limits in your Docker Compose file.

    services:
      app:
        deploy:
          resources:
            limits:
              memory: 2G
    

Debugging Commands

Additional Debugging Tips


Example Repository: uv-docker-starter

To get hands-on experience with UV and Docker, the uv-docker-starter repository provides a complete setup, including:

Clone the Repository

git clone https://github.com/loftwah/uv-docker-starter.git
cd uv-docker-starter

Repository Structure

uv-docker-starter/
├── README.md               # Guide and setup instructions
├── docker-compose.yml      # Compose configurations for both examples
├── examples/
│   ├── official/           # Example using official UV Docker image
│   │   ├── Dockerfile
│   │   ├── app/
│   │   │   └── main.py
│   │   └── requirements.txt
│   ├── custom/             # Example with custom UV integration
│   │   ├── Dockerfile
│   │   ├── pyproject.toml
│   │   ├── uv.lock
│   │   └── app/
│   │       └── main.py
├── .github/
│   ├── workflows/
│   │   └── build-and-push.yml # CI/CD pipeline for GitHub Actions

Key Components

1. Official UV Example

Navigate to the examples/official directory for a quick-start example using the official UV Docker image.

Features:

How to Run:

# Build and run locally
docker build -t uv-official ./examples/official
docker run -p 8000:8000 uv-official

2. Custom UV Example

For production-ready builds, use the examples/custom directory.

Features:

How to Run:

# Build and run locally
docker build -t uv-custom ./examples/custom
docker run -p 8001:8000 uv-custom

CI/CD Workflow with GitHub Actions

The repository includes a pre-configured GitHub Actions workflow located at .github/workflows/build-and-push.yml. This automates:

  1. Building multi-platform images using Docker BuildX.
  2. Running tests to ensure code quality.
  3. Pushing images to GitHub Container Registry (GHCR).

Workflow Highlights:

Trigger: The workflow runs on every push to the main branch.

View Workflow: build-and-push.yml

Using the Repository for Your Projects

  1. Clone or Fork the repository to customize it for your project.
  2. Replace the Example Application Code in examples/official/app/ or examples/custom/app/.
  3. Modify requirements.txt or pyproject.toml as needed.
  4. Update the CI/CD Workflow to match your repository and image tags.
  5. Integrate Additional Tools like monitoring, logging, and testing frameworks as per your requirements.

Conclusion

Choosing between the official UV Docker images and custom UV integration depends on your specific needs:

Start with the official images to get up and running quickly, and consider transitioning to custom integration as your project grows in complexity and demands higher performance and security.

You now have a comprehensive toolkit for building Python applications with UV and Docker. This setup provides:

Remember to regularly update UV and your dependencies to get the latest improvements and security fixes.


Additional Resources

Acknowledgements

Special thanks to the open-source community for continuously improving tools like UV and Docker, making modern Python development efficient and enjoyable.