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:
Tool | Average Build Time | Impact on Productivity |
---|---|---|
pip | ~3 minutes | Fragmented development |
uv | ~30 seconds | Integrated 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:
- Unified development environment: All developers started using the same Docker setup
- Easier onboarding: Much faster and simpler setup for new contributors
- Hot-reload development: Enabled the implementation of hot-reload in Docker
- More reliable dependency resolution: Fewer conflicts and compatibility issues
- 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:
-
Optimization for different use cases:
- Development Dockerfile: Prioritizes build speed and ease of development
- Production Dockerfile: Prioritizes security, image size, and stability
-
Development-specific tools:
- The development environment includes tools like debuggers and hot-reload
- The production environment is leaner and optimized
-
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:
- Tools matter: Choosing the right tools can dramatically transform productivity
- Rust for performance: Systems languages like Rust can bring significant benefits to development tools
- Experimentation is worth it: Trying new approaches, even unconventional ones, can lead to major improvements
- 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."