Creating a Python venv from RPM Wheels with uv
Introduction
Section titled “Introduction”Creating Python virtual environments in containerized or offline environments can be challenging. One powerful approach is to leverage RPM package managers to download Python wheels, then use uv to create a lightweight, reproducible virtual environment from those pre-downloaded wheels.
This workflow is particularly useful when:
- Building containerized applications with strict dependency control
- Working in air-gapped networks without internet access
- Creating reproducible builds across multiple machines
- Optimizing layer caching in Docker builds
The Workflow
Section titled “The Workflow”Step 1: Generate Your Current Dependencies
Section titled “Step 1: Generate Your Current Dependencies”Start by capturing your current Python environment:
pip freeze > requirements.txtThis creates a file with pinned versions of all installed packages, like:
Flask==3.0.0Werkzeug==3.0.1click==8.1.7itsdangerous==2.1.2Jinja2==3.1.2MarkupSafe==2.1.3Step 2: Download Wheels Using dnf
Section titled “Step 2: Download Wheels Using dnf”Extract just the package names and use dnf download to fetch all wheels:
mkdir -p ./wheelsdnf download --destdir ./wheels $(pip freeze | cut -d '=' -f 1)Breaking this down:
pip freeze | cut -d '=' -f 1- Extracts just the package names fromrequirements.txtdnf download- Uses the DNF package manager to download wheels--destdir ./wheels- Specifies where to save the wheels
After running this, your ./wheels directory will contain all the .whl files needed for your environment.
Step 3: Create a venv with uv
Section titled “Step 3: Create a venv with uv”Once you have all the wheels, create your virtual environment using uv:
uv venv venv --offlineuv pip install --offline ./wheels/*.whlOr more concisely, if your versions are already pinned:
uv venv myenvuv pip install --find-links ./wheels -r requirements.txtThe --find-links option tells uv to search for packages in your wheels directory before trying to fetch from PyPI.
Complete Example
Section titled “Complete Example”Here’s a practical example combining all steps:
#!/bin/bash
# Create project structuremkdir -p my-python-project/wheelscd my-python-project
# Capture current dependenciespip freeze > requirements.txt
# Download all wheelsdnf download --destdir ./wheels $(pip freeze | cut -d '=' -f 1)
# Create virtual environment with uvuv venv venvuv pip install --find-links ./wheels -r requirements.txt
# Activate the venvsource venv/bin/activatepython --versionDocker Integration
Section titled “Docker Integration”This approach shines in Docker builds due to layer caching:
FROM fedora:latest
# Install uv and dnf development toolsRUN dnf install -y python3-pip uv
WORKDIR /app
# Copy requirementsCOPY requirements.txt .
# Download wheels layer (caches well if requirements.txt doesn't change)RUN mkdir -p wheels && \ dnf download --destdir ./wheels $(cat requirements.txt | cut -d '=' -f 1)
# Create venv with uv (uses cached wheels)RUN uv venv venv && \ venv/bin/uv pip install --find-links ./wheels -r requirements.txt
# Copy application codeCOPY . .
ENTRYPOINT ["venv/bin/python"]This Dockerfile separates the slow “download wheels” layer from the application code, so code changes don’t trigger wheel re-downloads.
Benefits
Section titled “Benefits”Reproducibility: Wheels are locked to specific versions, ensuring the same environment everywhere.
Offline Installation: Once wheels are downloaded, you can create environments without internet access.
Layer Caching: In Docker, wheel downloads cache separately from application code.
Fast Iteration: Subsequent environment creation from cached wheels is much faster than downloading from PyPI.
Minimal Disk Usage: uv creates lightweight venvs compared to other tools.
Troubleshooting
Section titled “Troubleshooting””dnf: command not found”
Section titled “”dnf: command not found””This workflow requires a DNF-based system (Fedora, RHEL, CentOS). For Debian/Ubuntu, use apt download instead:
mkdir -p ./wheelsfor pkg in $(pip freeze | cut -d '=' -f 1); do apt download "$pkg" -o Dir::Cache=./wheelsdoneHowever, note that this downloads .deb files, not wheels. For true wheel-based workflows on Debian, use pip download with a requirements file instead:
pip download -d ./wheels -r requirements.txtBinary Extension Issues
Section titled “Binary Extension Issues”Some packages include compiled extensions. If you encounter architecture mismatches:
- Ensure you’re downloading wheels for the correct Python version
- Specify the platform explicitly:
pip download -d ./wheels --platform manylinux2014_x86_64 -r requirements.txt - Consider building wheels explicitly:
pip wheel -w ./wheels -r requirements.txt
Version Conflicts
Section titled “Version Conflicts”If dnf download can’t find a specific package:
- Check if it exists in the repository:
dnf search package-name - The package might only be available on PyPI, not in the distro repos
- Fall back to
pip downloadfor those packages
Alternative: Pure pip Approach
Section titled “Alternative: Pure pip Approach”If you prefer a simpler, distribution-agnostic approach, use pip download directly:
mkdir -p ./wheelspip download -d ./wheels -r requirements.txt
# Then create your venvuv venv myenvuv pip install --find-links ./wheels -r requirements.txtThis is more portable but gives you less control over the source of each package.
Conclusion
Section titled “Conclusion”Using dnf download with uv provides a powerful, reproducible workflow for Python environment management. It’s especially valuable in containerized deployments and offline scenarios, where you want to pre-download dependencies and cache them effectively.
The key insights are:
- Separate dependency downloading from venv creation for better layer caching
- Use
--find-linksto point uv to your pre-downloaded wheels - Pin versions in
requirements.txtfor true reproducibility - Combine with Docker for optimal build performance