Dockerizing UV - ISE Developer Blog (original) (raw)

Containerizing your Python applications helps standardize environments and streamline deployment—but it also introduces unique challenges. In our team’s exploration of modern dependency management tools, we discovered that while UV (Universal Virtualenv) delivers impressive speed and simplicity in local development, replicating its setup inside Docker requires careful planning. Drawing from our retrospective discussions and hands-on experience, this article dives into the common pitfalls of integrating UV with Docker and outlines practical solutions to ensure your containerized builds remain fast, efficient, and reproducible.

Best Practices for Defining UV and Python Dependencies in Docker

A multi-stage Docker build is highly recommended for UV. Here are key strategies to optimize your Dockerization process:

FROM python:3.11-bullseye AS build
RUN pip install --no-cache-dir uv

This will install UV globally in the build stage. Alternatively, use the official install script (which can handle cases where a wheel isn’t available). For example:

FROM python:3.11-bullseye AS build
RUN apt-get update && apt-get install -y curl build-essential
ADD https://astral.sh/uv/install.sh /tmp/install_uv.sh
RUN bash /tmp/install_uv.sh && rm /tmp/install_uv.sh

The above ensures that if UV needs to compile from source, the tools are available (dev.to). After this, the uv command is ready to use in the build container.

FROM python:3.11-slim AS base
WORKDIR /app
COPY pyproject.toml uv.lock* ./
# Install dependencies using UV based on your lock file
RUN uv sync --locked --all-extras
# Copy the remainder of the application code
COPY . .
CMD ["python", "app.py"]

In this snippet, the dependency files are copied first. This ensures that if only your application code changes while dependencies remain the same, Docker can cache the layer where dependencies were installed, leading to faster rebuilds.

Example Dockerfile Configuration

The Dockerfile below manages Python dependencies with UV in a multi-stage build process:

# Use a Python image with uv pre-installed
FROM mcr.microsoft.com/cbl-mariner/base/python:3 AS base

# Makes installation faster
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
ENV PATH="/usr/app/.venv/bin:$PATH"

# Set the working directory
WORKDIR /usr/app
COPY . .
RUN ls -la

# Install necessary build tools for compiling dependencies
RUN bash scripts/setup/Dockerfile/base.ba.sh

# Development Stage
FROM base AS dev
RUN bash scripts/uv/sync.ba.sh

# Testing Stage
FROM dev AS tested
RUN uvx pypyr ci_docker

# Release Stage: Sync production dependencies and freeze versions
FROM base AS release
RUN uv sync --locked --no-dev && uv pip install .
RUN uv pip freeze

# Final Service Stage: Configure runtime environment and expose port
FROM release AS service
EXPOSE 5000
ENTRYPOINT [ "flask" ]
CMD ["run", "--host=0.0.0.0"]

This multi-stage Dockerfile, which we have actively used in our current work, not only speeds up build times through efficient caching and isolation but also guarantees that our Docker images are consistent with our UV-managed local environments. Feel free to adjust the scripts and commands based on your CI/CD and production requirements.

Troubleshooting & Common Pitfalls

One of the challenges of using UV was integrating it into our containerization strategy with Docker. Using UV inside Docker containers can boost build performance, but it introduces some unique challenges.

RUN uv venv /opt/venv && uv pip install -r requirements.txt  
# OR  
RUN uv pip install --system  
ENV PATH="/opt/venv/bin:$PATH" VIRTUAL_ENV="/opt/venv"  

Conclusion

Integrating UV into a Docker-based workflow can unlock significant performance improvements. By following best practices like multi-stage builds, precise dependency copying, and environment configuration, you can overcome common challenges and harness UV’s speed and simplicity in your containerized projects.

The feature image was generated using Bing Image Creator. Terms can be found here.