Python Environment Management for Data Engineers: venv, pip, conda, Poetry, uv, Docker, requirements.txt, Lockfiles, pyproject.toml, and the 2026 Decision Framework

Table of Contents

Our Packaging & CLI Tools post covered building and distributing packages. This post goes deeper into the environment that runs your code — how to isolate dependencies, manage Python versions, lock reproducible installs, and containerize pipelines for production. If you have ever had a pipeline work on your laptop but fail in production because of a different pandas version, this post is for you.

Analogy — Separate kitchens for separate restaurants. Without environment management, every Python project shares one kitchen (system Python) — one chef adds salt (pandas 1.5), another adds sugar (pandas 2.0), and the soup is ruined (import errors). Virtual environments give each project its own kitchen with its own ingredients. Docker goes further — it gives each project its own building with its own kitchen, plumbing, and electricity (OS, libraries, Python version).

Why Environments Matter for Data Engineers

Data engineers run multiple projects simultaneously: a Spark ETL pipeline (needs pandas 1.5 + PySpark 3.5), a REST API (needs pandas 2.0 + FastAPI), and a ML model (needs pandas 2.0 + scikit-learn 1.4). Without isolation, installing pandas 2.0 for the API breaks the Spark pipeline that needs 1.5. This is called dependency hell, and it is the number one reason data engineering pipelines fail when moving from development to production.

ProblemWithout EnvironmentsWith Environments
Version conflictspandas 2.0 overwrites pandas 1.5Each project has its own pandas version
“Works on my machine”Your laptop has Python 3.11, prod has 3.9Docker ensures identical Python + packages
Reproducibility`pip install pandas` installs latest (changes weekly)Lockfile pins exact versions (pandas==2.1.4)
OnboardingNew team member spends 2 days setting up`uv sync` or `docker compose up` — done in 30 seconds

The Tool Landscape in 2026

ToolWhat It DoesBest ForSpeed
**venv + pip**Built-in virtual env + package installerSimple scripts, beginners, no extra dependenciesSlow
**pip-tools**Generates locked requirements.txt from requirements.inTeams standardized on pipMedium
**conda**Package manager + env manager + manages non-Python libraries (C, CUDA)Data science with GPU/native librariesMedium
**Poetry**Project manager with lockfiles, dependency groups, build/publishLibraries, established teamsMedium
**uv**Rust-based all-in-one (replaces pip, venv, pyenv, pip-tools)New projects, speed-critical CI/CD, recommended 2026 default10-100x faster than pip
**Docker**Containerization — packages OS + Python + dependencies + codeProduction deployments, CI/CD, team consistencyN/A (different layer)

venv + pip — The Built-in Approach

venv creates isolated Python environments. pip installs packages into them. Both ship with Python — no extra installation needed. This is the approach every Python developer should understand first, even if they use something more advanced in practice.

Creating and Using Virtual Environments

# Create a virtual environment in the "venv" folder
python3 -m venv venv

# Activate it (Mac/Linux)
source venv/bin/activate

# Activate it (Windows)
venv\Scripts\activate

# Your prompt changes to show the active environment
# (venv) $ python3 --version

# Install packages (isolated to this environment)
pip install pandas sqlalchemy pyarrow

# Check what is installed
pip list

# Freeze exact versions to a file
pip freeze > requirements.txt

# Deactivate when done
deactivate

requirements.txt — Pinning Dependencies

# requirements.txt (generated by pip freeze)
pandas==2.1.4
sqlalchemy==2.0.25
pyarrow==14.0.2
numpy==1.26.3

# Install from requirements.txt (on another machine or in CI)
pip install -r requirements.txt

# Best practice: pin exact versions (==) for applications
# Use >= only for libraries that others will install alongside their own dependencies

The Problem with pip freeze

pip freeze captures everything — including transitive dependencies (packages installed by other packages). If you pip install pandas, freeze captures pandas plus numpy, python-dateutil, pytz, six, and every sub-dependency. This makes the requirements.txt fragile: upgrading pandas might conflict with the pinned numpy version. This is why tools like pip-tools, Poetry, and uv exist.

pip-tools — Locked Requirements the Simple Way

pip-tools splits your dependencies into two files: requirements.in (what you want) and requirements.txt (what you get, with exact versions for everything).

# Install pip-tools
pip install pip-tools

# requirements.in -- YOUR direct dependencies (what you want)
# pandas>=2.0
# sqlalchemy>=2.0
# pyarrow>=12.0
# requests>=2.28

# Compile: resolves all dependencies and pins exact versions
pip-compile requirements.in
# Generates requirements.txt with every package and version pinned

# Install the locked versions
pip-sync requirements.txt
# pip-sync REMOVES packages not in requirements.txt (unlike pip install -r)

# Update a specific package
pip-compile --upgrade-package pandas

conda — For Data Science with Native Libraries

conda manages both Python packages and non-Python system libraries (C compilers, CUDA, HDF5, OpenSSL). This makes it essential for data science workloads that need GPU support, scientific computing libraries, or packages that pip cannot install without a C compiler.

# Create a conda environment with a specific Python version
conda create -n my-pipeline python=3.11

# Activate
conda activate my-pipeline

# Install packages (from conda-forge channel -- more packages, better maintained)
conda install -c conda-forge pandas sqlalchemy pyarrow

# Install from a YAML environment file
# environment.yml:
# name: my-pipeline
# channels:
#   - conda-forge
# dependencies:
#   - python=3.11
#   - pandas>=2.0
#   - sqlalchemy>=2.0
#   - pyarrow>=12.0
#   - pip:
#     - my-private-package  # pip packages that conda doesn't have

conda env create -f environment.yml

# Export environment for reproducibility
conda env export > environment.yml

# List environments
conda env list

# Remove an environment
conda env remove -n my-pipeline

When to use conda over pip/uv: When you need GPU libraries (CUDA, cuDNN), native scientific libraries (BLAS, LAPACK, MKL), or non-Python tools installed alongside Python. For pure Python data engineering (pandas, SQLAlchemy, boto3), pip/uv is simpler and faster.

Poetry — Project Management with Lockfiles

Poetry manages dependencies, virtual environments, lockfiles, and package building in one tool. It uses pyproject.toml for configuration and generates a poetry.lock file that pins every dependency to an exact version.

# Install Poetry
pip install poetry

# Create a new project
poetry new my-etl-project
# Creates: pyproject.toml, README.md, src/my_etl_project/__init__.py, tests/

# Add dependencies
poetry add pandas sqlalchemy pyarrow
poetry add --group dev pytest pytest-cov black

# Install all dependencies (reads pyproject.toml and poetry.lock)
poetry install

# Run commands inside the virtual environment
poetry run python my_script.py
poetry run pytest

# Update dependencies
poetry update pandas

# Build and publish
poetry build
poetry publish
# pyproject.toml (Poetry format)
[tool.poetry]
name = "my-etl-project"
version = "1.0.0"
description = "ETL pipeline tools"

[tool.poetry.dependencies]
python = "^3.9"
pandas = "^2.0"
sqlalchemy = "^2.0"
pyarrow = "^12.0"

[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
black = "^23.0"

uv — The 2026 Default

uv is a Rust-based tool from Astral (the company behind the Ruff linter, acquired by OpenAI in 2026) that replaces pip, pip-tools, venv, pyenv, and pipx in a single binary. It is 10-100x faster than pip and is the recommended starting point for new Python projects in 2026.

Analogy — A smartphone replacing five separate devices. Before smartphones, you carried a phone, camera, MP3 player, GPS, and calculator. uv is the smartphone — one tool that does everything the five separate tools did, faster and better.

# Install uv (standalone binary -- no Python needed)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Create a new project
uv init my-pipeline
cd my-pipeline

# Add dependencies (10-100x faster than pip)
uv add pandas sqlalchemy pyarrow
uv add --dev pytest pytest-cov

# Install all dependencies (reads pyproject.toml and uv.lock)
uv sync

# Run commands
uv run python my_script.py
uv run pytest

# Manage Python versions (replaces pyenv)
uv python install 3.11
uv python install 3.12
uv python pin 3.11  # Set project's Python version

# pip-compatible mode (drop-in replacement for pip)
uv pip install pandas
uv pip freeze
uv pip compile requirements.in -o requirements.txt

uv vs Poetry vs pip Comparison

Featurepip + venvPoetryuv
**Speed**Baseline (slow)3-5x faster than pip10-100x faster than pip
**Lockfile**None (use pip-tools)poetry.lockuv.lock
**Python version management**Needs pyenvNoBuilt-in (`uv python install`)
**Virtual env management**Manual (`python -m venv`)AutomaticAutomatic
**Dependency groups**Manual (multiple requirements files)Built-in groupsBuilt-in groups
**Build and publish**Needs build + twineBuilt-inBuilt-in
**CI/CD speed**Minutes30-60 seconds5-15 seconds
**Recommended for**Beginners, simple scriptsLibraries, established teamsNew projects (2026 default)

Docker — Production Isolation

Docker containerizes your entire environment: OS, Python version, system libraries, Python packages, and your code. The container runs identically on your laptop, in CI/CD, and in production — eliminating “works on my machine” entirely.

Analogy — A shipping container. Before shipping containers, cargo was loaded loose — it broke during transit, got mixed up, and took forever to unload. A container packages everything in a standard box that any truck, ship, or crane can handle. Docker does the same for your code.

Dockerfile for a Data Pipeline

# Use a slim Python base image
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Install system dependencies (if needed for pyodbc, psycopg2, etc.)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Copy dependency files first (Docker caches this layer)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY src/ src/
COPY configs/ configs/

# Set the entry point
ENTRYPOINT ["python", "-m", "etl_tools.cli"]
# Build the image
docker build -t etl-pipeline:1.0 .

# Run the pipeline
docker run etl-pipeline:1.0 run --source orders --target warehouse

# Run with environment variables (for database credentials)
docker run \
  -e DB_HOST=mydb.server.com \
  -e DB_USER=pipeline \
  -e DB_PASSWORD=secret \
  etl-pipeline:1.0 run --source orders

Dockerfile with uv (Faster Builds)

FROM python:3.11-slim

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app

# Copy project files
COPY pyproject.toml uv.lock ./

# Install dependencies (cached, 10-100x faster than pip)
RUN uv sync --frozen --no-dev

# Copy application code
COPY src/ src/

ENTRYPOINT ["uv", "run", "python", "-m", "etl_tools.cli"]

Decision Framework — Which Tool to Use

ScenarioRecommended ToolWhy
Quick script, learning Python`python -m venv` + `pip`Built-in, no setup needed
New data engineering project`uv`Fastest, manages everything, 2026 standard
Existing project using PoetryStay on PoetryStable, no migration pressure
Data science with GPU/CUDA`conda` (or `pixi`)Manages native libraries pip cannot
Production deploymentDocker + `uv` (or `pip`) insideIdentical environment everywhere
CI/CD pipelineDocker + `uv sync –frozen`Fastest installs, reproducible
Publishing a library to PyPIPoetry or `uv`Both have build/publish support
Team with mixed skill levels`uv`Simpler than Poetry, faster than pip

Common Mistakes

1. Installing packages in system Python. Running pip install pandas without a virtual environment installs into the system Python, affecting every project on the machine. Always create a virtual environment first — even for quick experiments.

2. Not pinning dependency versions. pip install pandas installs the latest version today. Next month, a new version with breaking changes ships. Pin versions in requirements.txt (pandas==2.1.4) or use a lockfile (poetry.lock, uv.lock) for reproducibility.

3. Committing the venv/ folder to Git. Virtual environment folders contain platform-specific binaries and are large (100+ MB). Add venv/ to .gitignore and commit only requirements.txt or pyproject.toml + lockfile. Teammates recreate the environment from the lockfile.

4. Using conda and pip together carelessly. Mixing conda install and pip install in the same environment can cause dependency conflicts because conda and pip use different dependency resolvers. If you must use both, install conda packages first, then pip packages last, and list pip packages in the pip: section of your environment.yml.

5. Not using Docker for production. “It works on my machine” is the most expensive sentence in data engineering. Docker ensures your pipeline runs on the exact same Python version, OS libraries, and package versions in production as it does on your laptop. Use Docker for any pipeline that runs on a schedule.

6. Rebuilding Docker images from scratch every time. Docker caches layers. If you copy your source code before installing dependencies, every code change invalidates the dependency cache. Copy requirements.txt first, install dependencies, then copy code — so dependency installation is cached across builds.

7. Not knowing about uv in 2026. uv replaces pip, venv, pyenv, and pip-tools with a single tool that is 10-100x faster. Many data engineers still use pip + venv because that is what they learned. For new projects, uv is the recommended default — it saves minutes per CI/CD run and seconds per install.

8. Not separating dev and production dependencies. Installing pytest, black, and jupyter in production wastes space and increases the attack surface. Use dependency groups ([tool.poetry.group.dev] or uv add --dev) to separate dev-only packages from production requirements.

Interview Questions

Q: What is a virtual environment and why do data engineers need them? A: A virtual environment is an isolated Python installation with its own packages, separate from the system Python and other projects. Data engineers need them because different projects require different package versions (one pipeline needs pandas 1.5, another needs 2.0). Without isolation, installing a package for one project can break another. Virtual environments prevent this by giving each project its own package directory.

Q: What is the difference between pip, Poetry, and uv? A: pip is Python’s built-in package installer — it installs packages but does not manage virtual environments, lockfiles, or Python versions. Poetry is a project manager that handles dependencies, lockfiles, virtual environments, and publishing — it uses pyproject.toml and generates poetry.lock. uv is a Rust-based all-in-one tool (released 2024, acquired by OpenAI 2026) that replaces pip, venv, pyenv, and pip-tools — it is 10-100x faster and manages Python versions, environments, lockfiles, and builds in a single binary. For new projects in 2026, uv is the recommended default.

Q: What is a lockfile and why is it important? A: A lockfile (poetry.lock, uv.lock, or requirements.txt from pip-compile) records the exact version of every package and every transitive dependency. Without a lockfile, pip install pandas>=2.0 might install pandas 2.1.4 today and 2.2.0 next month, potentially breaking your pipeline. A lockfile ensures that every install — on your laptop, in CI, in production — uses identical versions. This is the foundation of reproducible builds.

Q: When would you use conda instead of pip or uv? A: Use conda when your project requires non-Python system libraries that pip cannot install — GPU toolkits (CUDA, cuDNN), linear algebra libraries (BLAS, MKL), scientific computing libraries (HDF5, NetCDF), or compiled C/C++ extensions that need specific system dependencies. For pure Python data engineering (pandas, SQLAlchemy, boto3, pyarrow), pip or uv is simpler and faster. Many teams use conda for the data science layer and pip/uv for the data engineering layer.

Q: How does Docker help with data pipeline deployment? A: Docker packages your code, Python version, system libraries, and all dependencies into a single container image. This image runs identically on your laptop, in CI/CD, and in production — eliminating “works on my machine” issues. For data pipelines, Docker ensures that the same pandas version, ODBC driver, and Python version are used everywhere. It also enables horizontal scaling (run multiple containers) and simple rollbacks (deploy a previous image version).

Q: What is the recommended Python environment setup for a new data engineering project in 2026? A: Use uv as the package and environment manager. Run uv init to create a project with pyproject.toml. Add dependencies with uv add pandas sqlalchemy. Pin Python version with uv python pin 3.11. The lockfile (uv.lock) is generated automatically. For production, use Docker with uv inside: copy pyproject.toml and uv.lock, run uv sync --frozen --no-dev, then copy your code. This gives you fast, reproducible builds with minimal setup.

Q: How do you structure a Dockerfile for fast, cached builds? A: Order layers from least-changing to most-changing. First, install system dependencies (rarely changes). Second, copy dependency files (pyproject.toml, requirements.txt) and install packages (changes when dependencies change). Third, copy application code (changes frequently). Docker caches each layer, so when only your code changes, the dependency installation step is cached and the build takes seconds instead of minutes.

Wrapping Up

Environment management is the foundation that everything else builds on. Start with venv + pip for learning. Move to uv for new projects — it is faster, simpler, and handles everything. Keep conda for GPU and scientific computing. Use Docker for production. And always, always, always use a lockfile. The difference between a pipeline that “works on my machine” and one that works everywhere is a pinned environment.

Related posts:Packaging & CLI Tools (argparse, click, pyproject.toml)Modules, Packages & Virtual EnvironmentsETL PatternsTesting with pytest



Naveen Vuppula is a Senior Data Engineering Consultant and app developer based in Ontario, Canada. He writes about Python, SQL, AWS, Azure, and everything data engineering at DriveDataScience.com.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top
Share via
Copy link