Migrar de poetry a uv y acelerar tus builds 10x
Aprende a migrar de poetry a uv de forma segura manteniendo dependencias exactas y acelera tus builds en Python 3.14 hasta 10 veces.
El ecosistema de empaquetado en Python ha experimentado transformaciones profundas en el último año. Durante mucho tiempo, Poetry se consolidó como la herramienta estándar para la gestión de dependencias, resolución de entornos virtuales y publicación de paquetes. Sin embargo, la llegada de uv cambió drásticamente las expectativas de velocidad en el flujo de trabajo. Desarrollado en Rust, reemplaza herramientas como pip, pip-tools, virtualenv y al propio Poetry con un incremento de rendimiento significativo. Decidir migrar de poetry a uv no es solo una cuestión de seguir tendencias, sino una mejora directa de productividad que reduce el tiempo de ejecución de CI/CD de minutos a pocos segundos en Python 3.14.7.
En este artículo, voy a mostrar el proceso práctico para realizar esta transición de forma estructurada, manteniendo la reproducibilidad exacta de las dependencias de tu proyecto y evitando incidentes en entornos de producción.
¿Por qué la transición de Poetry a herramientas modernas se volvió esencial?
Poetry utiliza un resolvedor de dependencias propio escrito en Python puro. Aunque es preciso para garantizar consistencia, el algoritmo de resolución (SAT solver) con frecuencia enfrenta severos cuellos de botella al lidiar con árboles de dependencias profundos o con múltiples paquetes pesados. En proyectos corporativos, una simple adición de biblioteca puede requerir decenas de segundos — o incluso minutos — solo para recalcular el árbol y actualizar el archivo de bloqueo.
uv aborda este problema reescribiendo todo el motor de resolución y descarga desde cero. Implementa un resolvedor de dependencias concurrente extremadamente rápido, capaz de efectuar la descarga y compilación de wheels en una fracción del tiempo consumido por herramientas tradicionales. Además de la velocidad, uv adopta por defecto los estándares oficiales de la Python Packaging Authority (PyPA), específicamente el PEP 621 para la definición de metadatos de proyectos dentro del archivo pyproject.toml.
Poetry, al haber surgido antes de la consolidación del PEP 621, utiliza una sección personalizada ([tool.poetry]) que aísla la configuración del proyecto del estándar moderno. La migración alinea tu código con las especificaciones universales de la comunidad, eliminando el acoplamiento con una herramienta específica.
¿Cómo migrar de poetry a uv en tu proyecto sin romper dependencias?

El mayor temor al cambiar de gestor de paquetes es alterar inadvertidamente la versión de una biblioteca transitiva que está corriendo en producción. El proceso de conversión debe realizarse de manera que preserve el estado actual de la aplicación, migrando las declaraciones primarias y regenerando el archivo de bloqueo con garantías equivalentes.
Paso 1: Convertir la sección de dependencias al estándar PEP 621
En Poetry, las dependencias se ubican bajo [tool.poetry.dependencies] y [tool.poetry.group.dev.dependencies]. En el estándar PEP 621, utilizado por uv, las dependencias de producción residen en project.dependencies, y los grupos de desarrollo se ubican en dependency-groups (según el PEP 735) o en project.optional-dependencies.
Puedes realizar la transición manualmente o utilizar el comando de automatización del propio uv para leer el proyecto actual. Para hacer la migración directa de la estructura en tu pyproject.toml, modifica la organización de las claves.
Estructura heredada en Poetry:
[tool.poetry]
name = "meu-sistema"
version = "0.1.0"
description = "API de processamento de dados"
authors = ["Dev Team <dev@empresa.com>"]
[tool.poetry.dependencies]
python = "^3.14"
fastapi = "^0.115.0"
uvicorn = {extras = ["standard"], version = "^0.30.0"}
[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
ruff = "^0.6.0"
Estructura convertida al estándar PEP 621 aceptado por uv:
[project]
name = "meu-sistema"
version = "0.1.0"
description = "API de processamento de dados"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.30.0",
]
[dependency-groups]
dev = [
"pytest>=8.0.0",
"ruff>=0.6.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Ten en cuenta que reemplazamos el backend de compilación poetry-core por hatchling (o flit_core), que son backends modernos y ligeros soportados por el ecosistema estándar.
Paso 2: Exportar el lockfile antiguo para garantizar la integridad
Para garantizar que ninguna subdependencia cambie de versión durante la primera sincronización con uv, la mejor estrategia es exportar el árbol exacto de Poetry a un archivo temporal de pip antes de generar el nuevo uv.lock.
Ejecuta el comando de exportación en Poetry:
poetry export -f requirements.txt --output requirements-temp.txt --with dev
A continuación, utiliza uv para compilar un nuevo archivo de bloqueo generado rigurosamente a partir de esas versiones exactas, garantizando que la resolución inicial sea idéntica a lo que existía en poetry.lock:
uv pip compile requirements-temp.txt -o requirements-resolved.txt
Ahora puedes generar el archivo uv.lock nativo ejecutando la inicialización del proyecto con uv:
uv lock
Tras validar la creación de uv.lock, el archivo poetry.lock y el archivo temporal requirements-temp.txt se pueden eliminar con seguridad del repositorio.
Paso 3: Crear el entorno virtual y sincronizar
Con la nueva configuración lista, elimina la carpeta del entorno virtual anterior gestionado por Poetry (generalmente ubicada en ~/.cache/pypoetry/virtualenvs o en la raíz en .venv) y deja que uv cree el nuevo entorno aislado:
# Elimina el entorno antiguo si está en la raíz
rm -rf .venv
# Crea el nuevo entorno virtual vinculado a Python 3.14.7
uv venv
# Sincroniza el entorno con las dependencias de uv.lock
uv sync
El comando uv sync realiza la instalación atómica y extremadamente rápida de todos los paquetes, dejando el .venv listo para su uso inmediato.
¿Cómo ajustar el pipeline de CI/CD para utilizar el nuevo gestor?
Uno de los mayores rendimientos al adoptar uv ocurre en las etapas de automatización e integración continua (CI/CD). El tiempo transcurrido preparando el entorno Python en los runners de la nube se reduce drásticamente debido al sistema de caché global de uv y a la ausencia de overhead de compilación.
En un pipeline típico de GitHub Actions, el paso de instalación que anteriormente requería la configuración de Python, la instalación de Poetry vía pip y la ejecución de poetry install se puede simplificar usando la action oficial astral-sh/setup-uv.
A continuación se muestra un ejemplo comparativo de un flujo de trabajo configurado para probar y validar el código con uv:
name: Pipeline de Integración Continua
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Instalar uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
enable-cache: true
- name: Configurar Python
uses: actions/setup-python@v5
with:
python-version: "3.14"
- name: Sincronizar dependencias
run: uv sync --all-groups
- name: Ejecutar pruebas
run: uv run pytest
La bandera enable-cache: true instruye a la action a almacenar en caché las capas de paquetes de uv. Como la descarga de paquetes wheels ocurre de forma paralela y optimizada en Rust, el tiempo de instalación de dependencias en el runner cae comúnmente de 45 segundos a menos de 3 segundos.
En un entorno de contenedorización con Docker, la optimización también es evidente. Mira cómo estructurar un Dockerfile enfocado en producción usando construcciones multietapa (multi-stage builds):
FROM python:3.14-slim AS builder
# Copia el ejecutable de uv directamente desde la imagen oficial
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
# Copia solo los archivos de definición para optimizar la caché de capas
COPY pyproject.toml uv.lock ./
# Sincroniza las dependencias sin instalar el proyecto actual y sin paquetes de dev
RUN uv sync --frozen --no-dev --no-install-project
FROM python:3.14-slim
WORKDIR /app
# Copia el entorno virtual compilado de la etapa anterior
COPY --from=builder /app/.venv /app/.venv
COPY .
ENV PATH="/app/.venv/bin:$PATH"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
La bandera --frozen garantiza que uv no intentará actualizar uv.lock durante la compilación del contenedor, fallando inmediatamente en caso de que el archivo de bloqueo esté desincronizado con pyproject.toml.
¿Cuáles son los principales problemas al cambiar de gestor y cómo resolverlos?

Aunque el proceso de migración es bastante directo, existen particularidades de configuración y comportamientos de borde que pueden generar errores si no se anticipan.
Manejo de repositorios privados e índices personalizados
Si tu proyecto depende de un servidor PyPI privado (como Nexus, Artifactory o AWS CodeArtifact), la configuración en Poetry se realizaba a través de los comandos poetry config repositories. En uv, la definición de índices privados se declara directamente en pyproject.toml usando la especificación [[tool.uv.index]] o mediante variables de entorno.
Ejemplo de declaración en pyproject.toml:
[[tool.uv.index]]
name = "repositorio-interno"
url = "https://pypi.empresa.com/simple/"
explicit = true
Al marcar explicit = true, evitas que uv busque paquetes públicos en el PyPI oficial para las dependencias registradas en este repositorio, previniendo ataques de tipo dependency confusion.
Gestión de scripts de línea de comandos
En Poetry, era común definir puntos de entrada en la sección [tool.poetry.scripts]. Con PEP 621, estos comandos deben reescribirse en la sección [project.scripts].
# Formato antiguo de Poetry
# [tool.poetry.scripts]
# mi-cli = "mi_paquete.cli:main"
# Formato estándar PEP 621 para uv
[project.scripts]
mi-cli = "mi_paquete.cli:main"
Al ejecutar uv pip install -e . o uv sync, el ejecutable mi-cli se instalará dentro del directorio bin del entorno virtual .venv y se podrá invocar con uv run mi-cli.
Diferencias en la resolución de paquetes editables
Los proyectos que utilizan monorrepos o dependencias locales vinculadas mediante -e (editable) requieren atención. En Poetry, las dependencias de ruta local se declaraban con { path = "../outro-pacote", develop = true }. En uv, la sintaxis sigue el estándar de fuentes de uv:
[tool.uv.sources]
otro-pacote = { path = "../outro-pacote", editable = true }
Esta separación explícita entre la declaración de la versión mínima en project.dependencies y el origen del código en tool.uv.sources mantiene el archivo compatible con especificaciones abiertas sin atar el repositorio a una herramienta propietaria.
Tabla comparativa y veredicto de funciones
Para visualizar de forma clara lo que cambia en el día a día del desarrollo tras reemplazar Poetry por uv, analiza la tabla con los principales aspectos operacionales de cada herramienta:
| Criterio | Poetry | uv (Astral) |
|---|---|---|
| Lenguaje del motor | Python puro | Rust |
| Estándar de metadatos | Sección propietaria [tool.poetry] |
Estándar oficial PEP 621 ([project]) |
| Archivo de bloqueo | poetry.lock |
uv.lock |
| Tiempo promedio de bloqueo | De 10s a 120s en proyectos medianos | Menos de 1s en casi todos los escenarios |
| Gestión de Python | Requiere pyenv o Python preinstalado |
Instala y gestiona versiones de Python nativamente (uv python install) |
| Ejecución de herramientas aisladas | Exige instalación previa mediante plugins | Ejecuta herramientas bajo demanda igual que npx (uvx ruff check) |
El veredicto técnico es claro: Poetry desempeñó un papel histórico fundamental al traer el concepto de archivos de bloqueo y gestión moderna de proyectos a Python. Sin embargo, la arquitectura de uv ofrece una superioridad técnica difícil de ignorar. La combinación de rendimiento extremo en Rust con la adhesión rigurosa a los estándares de la PyPA convierte a uv en la elección ideal para nuevos proyectos y en una actualización de alta prioridad para bases de código existentes.
Conclusión
Decidir migrar de poetry a uv es una de las inversiones de infraestructura de código con mayor rendimiento inmediato para equipos de Python. Al adoptar los estándares de PEP 621 y aprovechar la velocidad del motor en Rust, eliminas retrasos frecuentes en pipelines de integración continua, simplificas la gestión de versiones del lenguaje y reduces la complejidad de tu herramienta de empaquetado. El flujo de conversión es seguro, totalmente reversible y garantiza la paridad exacta de dependencias necesaria para mantener la estabilidad de tu aplicación en producción.