Tired of waiting for pip? UV cuts dependency hell from coffee-break to blink-speed. Here's how I use it for MLOps—and why you should too.
🔗 Originally published on Medium. Shared here because dev.to folks know good tooling when they see it!
TL;DR (What’s In This Guide)
- 🚀 UV is like pip on steroids: Installs Python deps 10x faster than Poetry.
- 🔒 Lockfiles that work: No more "works on my machine" nonsense.
- 🐳 Docker magic: Build slim images without the headache.
- 🤖 CI/CD simplified: GitHub Actions that don’t make you want to cry.
- 👉 Try it now: mlops-uv
"why UV over Poetry/Pipenv/that-cool-tool-I-saw-on-HN?" Glad you asked. Let’s get hands-on.
How to use this guide
The supporting repo: mlops-uv
- Build the project from scratch by manually setting up the structure and copy-pasting the provided code base (src and tests folders).
- Clone the repository, install dependencies using the command
uv sync\
, and run the commands explained below directly to:
- Execute the test suite
- Build the Docker image
- Modify and test GitHub Actions
Introduction
MLOps (Machine Learning Operations) is all about bringing DevOps principles into machine learning, making model deployment, versioning, and monitoring more efficient. However, managing dependencies, ensuring reproducibility, and streamlining deployments can be a major headache for ML/DS teams.
That’s where UV comes in — a fast, modern package manager that simplifies dependency management, build processes, and CI/CD for Python projects.
In this article, we’ll explore how UV can enhance MLOps workflows through AceBet, a mock-up FastAPI app that predicts the winner of an ATP match (for demonstration purposes only — don’t bet your savings on it!). We’ll cover:
- Setting up a UV-based MLOps project
- Managing dependencies and lockfiles
- Automating CI/CD with GitHub Actions
- Building and deploying with Docker
Let’s dive in!
Make sure to read:
for a smoother reading of the part 3.
📦 Initializing an MLOps Project with UV
When working on an MLOps project, structuring your codebase properly is crucial. We’ll start by setting up a packaged application using UV:
uv init --package acebet
A packaged application follows the src-based structure, where the source code is contained within a dedicated package directory (src/acebet
). This approach is beneficial for:
✅ Large applications with multiple modules
✅ Projects that need to be distributed (e.g., PyPI packages, CLI tools)
✅ Better namespace isolation, preventing import conflicts
✅ Improved testability and modularity
example-pkg/
├── src/
│ ├── example_pkg/
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── utils.py
├── tests/
│ ├── test_module.py
├── pyproject.toml
└── README.md
This structure ensures:
✔ Encapsulation: The application is a proper Python package, avoiding accidental name conflicts.
✔ Reusability: Can be installed via pip install .
or published to PyPI.
✔ Cleaner Imports: Enforces absolute imports (from example_pkg.utils import foo
) instead of relative imports.
✔ Better CI/CD Support: Easier to package and distribute in Docker, PyPI, or GitHub Actions.
👉 For quick scripts or internal projects? Use a regular application.
👉 For scalable, maintainable, and deployable projects? Use a packaged application.
In our case, we will re-use AceBet, a simple FastAPI application following basic MLops principles. Including several modules for preparing the data, training the model, predicting and defining the endpoints, and a test suite.
A packaged app is therefore the best choice (and will be for anything else than POC)
🔧 Managing Dependencies with UV
Installing Core Dependencies
Once your project is initialized, install the necessary dependencies for developing AceBet, including FastAPI and machine learning libraries like Scikit-learn:
uv add fastapi scikit-learn pandas lightgbm
and any other packages required for the application to run properly. UV will take care of the resolution of the needed versions.
Creating a Lockfile for Reproducibility
One of UV’s key advantages is ensuring dependency reproducibility with a lockfile. This guarantees that all environments (local, staging, production) use the same dependency versions.
Once you are satisfied with the first version of the code base, generate a lockfile:
uv lock
Or, if you want to sync all dependencies in one go:
uv sync
This process ensures that dependency versions remain consistent across different environments — an essential practice in MLOps.
🛠 Adding Testing Dependencies & Running Tests
In MLOps, testing is just as important as model accuracy. UV provides 3 different and very convenient ways to run tests or use tools (a tool is usually a Python CLI such as pytest):
- If you need a tool as part of your Python project, add it as a dependency (
uv add --dev
), they will be listed in thepyproject.toml
as using other project managers. - If you only need to run a tool occasionally, execute it with
uvx
. - If you need a tool persistently in your system or Docker, install it using
uv tool install
.
You can add testing libraries using:
uv add --dev pytest
These dependencies will then be distributed as development dependencies with your application distribution.
A final piece of advice on how to choose the method:
The pyproject.toml
should explicitly list all required dependencies for reliable test suite distribution, guaranteeing that developers use the same versions. We opt for the uv add --dev
method, but the uv tool install
will come in handy for Docker.
🚀 Automating CI/CD with GitHub Actions
Now that our application is running and tested properly, we want to ensure integrity if new commits are merged to the main branch.
A robust CI/CD pipeline ensures your models and applications are always production-ready. With UV, setting up GitHub Actions is straightforward.
Astral provides a GitHub Actions workflow that installs dependencies and runs tests automatically on every push to the main
branch. A simple example would be running the test suite for each new commit on the main branch:
name: Testing
on:
push:
branches:
- "main"
jobs:
uv-example:
name: Python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install UV
uses: astral-sh/setup-uv@v5
- name: Install the project
run: uv sync --all-extras --dev
- name: Run tests
run: uv run pytest tests
This workflow:
✅ Installs UV
✅ Syncs dependencies
✅ Runs unit tests using Pytest
You can refine and add a Python matrix, and further sophistication
🐳 Building a Docker Image with UV
A well-built Docker image simplifies deployment and ensures your application runs consistently in any environment. UV makes it easy to containerize an application.
Here’s a basic Dockerfile to containerize AceBet:
FROM python:3.12-slim
# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Copy the application into the container
COPY . /app
# Set working directory
WORKDIR /app
# Install dependencies
RUN uv sync --frozen --no-cache
# Run the FastAPI app
CMD ["/app/.venv/bin/fastapi", "run", "src/acebet/app/main.py", "--port", "80", "--host", "0.0.0.0"]
For production-ready builds, use a multi-stage Docker build to keep the final image lightweight.
🌟 Why UV for MLOps?
🎯 Conclusion
By integrating UV into your MLOps workflow, you get a fast, reproducible, and efficient setup for managing dependencies, testing, and deployment.
With AceBet, we demonstrated how to:
✔️ Initialize a structured UV project
✔️ Manage dependencies & lockfiles
✔️ Automate testing with GitHub Actions
✔️ Build Docker images for deployment
If you’re working with Python-based MLOps projects, give UV a try — it might just replace Pip and Poetry in your workflow! 🚀