UV vs PIP: Speeding Up Python Builds

The Build Speed Problem

One of the challenges that significantly impacted our productivity was the Docker build time during Python development. The traditional process of installing Python dependencies using pip was taking approximately 3 minutes for each full build.

graph LR
    subgraph "Performance Comparison"
        P["pip<br>~3 minutes"] --> C{Comparison}
        U["uv<br>~30 seconds"] --> C
    end
    
    subgraph "Development Impact"
        C --> D["Fragmented<br>Development"]
        C --> I["Integrated<br>Development"]
        
        D --> DL["Local Python"]
        D --> DB["Separate DB"]
        D --> DF["Separate Frontend"]
        
        I --> ID["Complete Docker"]
        I --> IH["Hot Reload"]
        I --> IC["Fast CI/CD"]
    end
    
    classDef pip fill:#ff9999,stroke:#333,stroke-width:2px
    classDef uv fill:#99ff99,stroke:#333,stroke-width:2px
    classDef fragmented fill:#ffcccc,stroke:#333,stroke-width:2px
    classDef integrated fill:#ccffcc,stroke:#333,stroke-width:2px
    
    class P pip
    class U uv
    class D,DL,DB,DF fragmented
    class I,ID,IH,IC integrated

This excessive build time had direct consequences:

  • Reduced productivity: Developers had to wait long periods after each change
  • Fragmented development: Many opted to develop with local Python, separate databases, and frontends
  • Additional complexity: Maintaining separate environments led to inconsistencies and hard-to-track bugs
  • Difficulty for new contributors: The setup process was complicated and time-consuming

Discovering UV: A Python Installer in Rust

During our search for solutions, we discovered UV, a Python dependency installer and resolver developed in Rust by the open-source community.

What Makes UV Special?

  • Developed in Rust: A language known for its performance and safety
  • Efficient parallelism: Installs and resolves dependencies simultaneously
  • Smart caching: Reuses downloaded packages efficiently
  • Compatibility: Works as a direct replacement for pip
  • Open-source: Actively maintained by the community

The Transformation: From 3 Minutes to 30 Seconds

Replacing pip with UV in our build process resulted in a dramatic reduction in build time:

ToolAverage Build TimeImpact on Productivity
pip~3 minutesFragmented development
uv~30 secondsIntegrated workflow

This performance improvement of approximately 6x completely transformed our development workflow.

Implementation in Dockerfile

The implementation was surprisingly simple. We replaced pip with UV in our development Dockerfile:

# Before (with pip)
FROM python:3.10-slim as dev
WORKDIR /app
COPY requirements-dev.txt .
RUN pip install --no-cache-dir -r requirements-dev.txt

# After (with uv)
FROM python:3.10-slim as dev
WORKDIR /app
COPY requirements-dev.txt .
RUN pip install --no-cache-dir uv
RUN uv pip install --no-cache-dir -r requirements-dev.txt

Benefits Beyond Speed

Adopting UV brought additional benefits beyond just reducing build time:

  1. Unified development environment: All developers started using the same Docker setup
  2. Easier onboarding: Much faster and simpler setup for new contributors
  3. Hot-reload development: Enabled the implementation of hot-reload in Docker
  4. More reliable dependency resolution: Fewer conflicts and compatibility issues
  5. Better resource utilization: More efficient CPU and memory usage during builds

Why Two Dockerfiles?

One question that arose was: "Why maintain two separate Dockerfiles (dev and prod) instead of unifying them?"

Our decision was based on several considerations:

  1. Optimization for different use cases:

    • Development Dockerfile: Prioritizes build speed and ease of development
    • Production Dockerfile: Prioritizes security, image size, and stability
  2. Development-specific tools:

    • The development environment includes tools like debuggers and hot-reload
    • The production environment is leaner and optimized
  3. Ease for contributors:

    • Developers can start the complete environment with a single command
    • No deep knowledge of infrastructure, CI/CD, or networking is required

This separation allowed us to optimize each environment for its specific purpose, making the project more accessible to new contributors.

Lessons Learned

This experience taught us valuable lessons:

  1. Tools matter: Choosing the right tools can dramatically transform productivity
  2. Rust for performance: Systems languages like Rust can bring significant benefits to development tools
  3. Experimentation is worth it: Trying new approaches, even unconventional ones, can lead to major improvements
  4. Open-source community: Leveraging community work can solve seemingly intractable problems

Impact on the Project as a Whole

Adopting UV was one of the biggest contributors to developer productivity and satisfaction in the project. What started as a technical optimization fundamentally transformed how we work and collaborate.


"Time is the most valuable resource we have; it's great that there's a way to compile Python in 30 seconds instead of 3 minutes."