Environments Best Practices
Practical rules for keeping Python environments isolated, reproducible, and team-friendly.
How to Use This List
- Apply these when starting a new repo or onboarding a teammate
- Enforce the CI-related items in pull request checks
- Revisit quarterly as tooling (uv, ruff) evolves
A - Isolation
- One virtual environment per project. Never install project deps into the system Python.
- Commit
.python-versionor pin inpyproject.toml. Everyone runs the same interpreter. - Add
.venv/to.gitignore. Environments are reproducible from lockfiles, not copied. - Document the three bootstrap commands in README. Clone, sync, test - nothing else required.
B - Reproducibility
- Commit the lockfile (
uv.lock,poetry.lock, orpdm.lock). CI and prod install identical packages. - Use
uv sync --frozen(or equivalent) in CI. Builds fail if the lockfile is stale. - Declare
requires-pythoninpyproject.toml. Block installs on unsupported interpreters. - Separate dev and prod dependency groups. Production containers exclude pytest, ruff, mypy.
C - Project Structure
- Use
src/layout with editable installs. Tests import the installed package, not the repo root. - Keep all config in
pyproject.toml. One file for metadata, deps, ruff, pytest, mypy. - Prefer
uvfor new projects. Fast installs, built-in lockfile, Python version management. - Use workspace mode for monorepos. Local packages reference each other without path hacks.
D - Security & Access
- Never commit credentials or tokens. Use env vars, CI secrets, or
~/.netrc. - Audit dependencies regularly. Run
pip-auditoruv pip auditin CI weekly. - Pin internal packages to a private index. Document index URL and auth for CI.
- Use HTTPS for all package indexes. No plain HTTP in production pipelines.
E - Team Workflow
- Standardize on one package manager per repo. No mixing pip, Poetry, and uv lockfiles.
- Run
uv lockafter any dependency change. Keeppyproject.tomland lockfile in sync. - Export
requirements.txtonly for Docker legacy stages. The lockfile is the source of truth. - Test the README bootstrap on a clean machine quarterly. Catch doc drift before new hires do.
FAQs
Should libraries commit lockfiles?
Libraries publish version ranges. Applications and services must commit lockfiles.
uv or Poetry for a new team?
uv for speed and simplicity. Poetry if the team already has mature Poetry workflows and training.
How do I migrate from requirements.txt?
Create pyproject.toml, import deps with uv add or pdm import, generate a lockfile, delete requirements.txt.
What goes in .gitignore besides .venv?
__pycache__/, *.egg-info/, .mypy_cache/, .ruff_cache/, dist/, .env.
Should CI create the venv or use a cached one?
Cache the venv keyed on the lockfile hash. Recreate when the hash changes.
How do I handle platform-specific deps?
Use environment markers in pyproject.toml: sys_platform == 'win32'.
One global venv for all projects?
No. Isolation prevents version conflicts between projects.
When should I upgrade Python?
When security support ends for your current version or when you need new language features. Test in a branch first.
How do I share env vars safely?
.env.example with dummy values in the repo. Real .env gitignored. CI uses secret stores.
What about Docker and venvs?
In Docker, install deps globally in the container (no venv needed). On the host, always use a venv.
Related
- Environments & Packaging Basics - getting started
- uv - The Fast Toolchain - recommended toolchain
- Dependency Resolution & Lockfiles - pinning policy
- Managing Python Versions - version strategy
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.