So, here I am staring at my terminal, watching Poetry hang on “Resolving dependencies…” for the tenth time today. Don’t get me wrong—Poetry was a massive leap forward for Python packaging. It saved us from the dark ages of `requirements.txt` chaos. But as my projects scaled and my CI/CD pipelines got heavier, the slow resolution times started to add up. That’s when I finally decided it was time to bite the bullet and figure out the process of migrating Poetry to UV package manager.
If you haven’t heard of it, uv is an extremely fast Python package and project manager written in Rust by the team at Astral (the same folks behind Ruff). It acts as a drop-in replacement for `pip`, `pip-tools`, `pipx`, `poetry`, `pyenv`, and `virtualenv`. The speed difference isn’t just noticeable—it’s staggering. We’re talking resolving dependencies in milliseconds instead of minutes.
Let’s figure this out together. I’m going to walk you through exactly how I migrated my production projects from Poetry to UV, avoiding the outdated tools and focusing on the modern, standard approaches.
Prerequisites: Getting UV Installed
Before we touch our `pyproject.toml` files, we need UV installed globally on our system. Forget about `pip install uv-pm` (that’s an outdated wrapper). You want the official binary.
# On macOS and Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# On Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"Method 1: The Automated Way (migrate-to-uv)
If you have a fairly standard Poetry setup without overly complex private indexes or custom plugins, the community-maintained `migrate-to-uv` tool is absolutely brilliant. It parses your `pyproject.toml` and automatically rewrites the `[tool.poetry]` blocks into standard PEP 621 `[project]` blocks.
You don’t even need to install it permanently; you can just run it using UV’s temporary execution tool, `uvx`.
# Navigate to your project directory
cd my-python-project
# Run the migration script
uvx migrate-to-uvThis script does three major things:
- It converts your Poetry dependencies into standard `dependencies` arrays.
- It preserves your Poetry dependency groups (like `dev` or `test`) by moving them to the `[dependency-groups]` section.
- It cleans up the old Poetry boilerplate.
Method 2: The Manual Native Way (My Preference)
Why did I decide to build it this way? Because relying on third-party conversion scripts can sometimes mess up intricate configurations. I strongly prefer doing it manually to ensure my `pyproject.toml` is perfectly aligned with standard Python packaging standards.
Step 1: Update the Build System
Poetry relies on `poetry-core` as its build backend. UV prefers standard backends like `hatchling`. Open your `pyproject.toml` and change the `[build-system]` section:
# Before (Poetry)
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
# After (Standard/UV)
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"Step 2: Convert to PEP 621 Standard
Poetry uses its proprietary `[tool.poetry]` tables. You need to convert this to the standard `[project]` table.
# Remove this:
[tool.poetry]
name = "my-app"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.100.0"
# Replace with this:
[project]
name = "my-app"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"fastapi>=0.100.0",
]Step 3: Handling Dev Dependencies
In Poetry, you might have `[tool.poetry.group.dev.dependencies]`. In UV, this simply becomes standard dependency groups:
[dependency-groups]
dev = [
"pytest>=7.0.0",
"ruff>=0.1.0",
]Generating the UV Lockfile
Once your `pyproject.toml` is cleaned up, it’s time to generate the new lockfile. First, completely delete your old Poetry files.
rm poetry.lock
rm -rf .venvNow, let UV work its magic. Run the sync command. This will resolve dependencies, generate a fresh `uv.lock` file, create a virtual environment (`.venv`), and install all packages in milliseconds.
uv syncIf you want to add a new package later, instead of `poetry add requests`, you simply run:
uv add requestsUpdating CI/CD Workflows (GitHub Actions)
This is arguably the best part of migrating Poetry to UV package manager. Your CI/CD pipelines will speed up dramatically. You no longer need heavy Python setup steps caching massive `.venv` folders.
Here is what a modern GitHub Actions workflow looks like with UV:
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv python install
- name: Install dependencies
run: uv sync --all-groups
- name: Run tests
run: uv run pytestNotice the command replacement: wherever you previously used `poetry run`, you now use `uv run`. It behaves exactly the same way, executing the command inside the isolated virtual environment.
Final Thoughts
Migrating from Poetry to UV was one of the highest ROI technical decisions I’ve made this year. It eliminated the dreaded dependency resolution hangs, sped up my CI/CD builds by over 60%, and allowed my team to drop non-standard `tool.poetry` configurations in favor of official PEP standards.
If you have a spare afternoon, create a new branch and test it out. Run `uvx migrate-to-uv`, type `uv sync`, and watch how fast your environment springs to life. You won’t look back.
