Mastering Python Project Management with uv: Part 3 — MLops
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:
For reliable test suite distribution, the pyproject.toml
should explicitly list all required dependencies, guaranteeing 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! 🚀
Happy Coding!