Blog #python #devops

conda vs poetry vs uv vs pip

How I combine conda, poetry, uv, and pip to keep local Python environments sane and make CI/Docker builds 10–100× faster.

Published
Updated
Reading
3 min

After spending a year debugging broken virtualenvs, slow Docker builds, and lockfile drift across ML repos, I split the work between conda, poetry, uv, and pip. Each tool does one job well and gets in the way when stretched into the others. Here is how I wire them together.

Local Development

  • Use Conda to manage Python versions and isolated environments
    • Conda is still hard to beat when you need multiple isolated Python versions (or non-Python system libraries) on one laptop.
    • Spin up a clean environment with a pinned Python interpreter: conda create -n local_env python=3.14
    • Activate it: conda activate local_env (your shell prompt switches to (local_env) $).
  • Use Poetry for dependency resolution (pyproject.toml + poetry.lock)
    • Add dependencies via Poetry so your pyproject.toml and poetry.lock stay deterministic: poetry add numpy
    • Note on virtualenvs: By default, Poetry creates its own virtualenv behind the scenes, which it only enters if you prefix commands with poetry run. On a dev machine where you’re already inside conda activate local_env, you only need Poetry for resolution and lockfile updates.
    • Once your dependencies are locked, install the project into your active Conda environment with pip install -e . (or pip install .). That gives you fast, editable local imports right inside local_env.

CI / DevOps / Docker Builds

  • Skip Conda in containers
    • Inside a Docker image or CI runner, the container is your isolation boundary, and poetry.lock already pins the exact dependency graph.
  • Why plain poetry install bottlenecks CI
    • Poetry’s dependency resolver is thorough, and locking dependencies with poetry.lock is great, but running poetry install on a heavy ML stack (PyTorch, CUDA wheels, scientific libraries) in every CI job is still noticeably slow.
  • Pair Poetry’s lockfile with uv for 10–100× faster installs
    • Astral’s uv is a Rust-based Python package installer that installs wheels in a fraction of the time pip or poetry install takes.
    • If your repo already standardizes on poetry.lock, you don’t have to rip Poetry out to get uv’s speed. Export the locked requirements in under a second and pipe them straight into uv pip install --no-deps:
      • uv pip install --no-deps -r <(poetry export --without-hashes -f requirements.txt)
    • Because poetry.lock already resolved the full transitive graph, --no-deps lets uv download and unpack the exact pinned wheels into your virtualenv.
  • The fastest production/CI sequence
    • Install all third-party wheels with uv, then let Poetry register the root package itself:
      • uv pip install --no-deps -r <(poetry export --without-hashes --with dev -f requirements.txt)
      • poetry install --only-root

Summary (TL;DR)

On your laptop (local development)

  • Conda to create and switch Python environments (conda create, conda activate).
  • Poetry to add packages and maintain pyproject.toml + poetry.lock.
  • Pip (pip install -e .) for editable local installs inside the active Conda environment.

In CI and Docker builds (production)

  • Keep pyproject.toml and poetry.lock as your single source of truth.
  • Use uv (10–100× faster than pip) to install the exported lockfile in seconds.
  • Run entrypoints via poetry run or uv run with no Conda base image needed.

When to Use All Four

Combining four tools sounds over-engineered until you’re maintaining a monorepo full of 5 GB GPU wheels where developers want Conda on their MacBooks, release engineers want strict lockfiles, and nobody wants to wait twelve minutes for a CI container to build. If you’re starting a greenfield repo today with no legacy poetry.lock requirement, uv lock + uv sync can handle the whole flow on its own.

Related work

Production ML context

See how this topic connects to production ML systems, infrastructure, and inference.