cheat sheet
Virtual Environments
Create, activate, and manage Python virtual environments with venv. Covers activation for every shell, pip usage, requirements files, and cleanup.
Virtual Environments — venv
What it is
venv is Python's built-in module for creating isolated virtual environments (Python 3.3+, no install required). Each environment gets its own site-packages directory so dependencies installed for one project cannot conflict with those of another or corrupt the system Python. It is the baseline tool for Python environment isolation — lightweight and always available — before reaching for higher-level managers like uv or poetry.
A virtual environment gives each project its own isolated Python installation. Never install packages into the system Python.
Create a virtual environment
python -m venv .venv
Output:
(no output — the .venv/ directory is created)
The conventional name is .venv or venv. Use .venv so editors (VS Code, PyCharm) auto-detect it.
Activate
Activation makes .venv/bin/python and .venv/bin/pip the active interpreter.
# bash / zsh
source .venv/bin/activate
# fish
source .venv/bin/activate.fish
# Windows PowerShell
.venv\Scripts\Activate.ps1
# Windows cmd.exe
.venv\Scripts\activate.bat
Output: (none — exits 0 on success)
After activation your shell prompt changes to show the environment name:
Output:
(.venv) user@host:~/myproject $
Check you're in the right environment:
which python
Output:
/home/user/myproject/.venv/bin/python
Install packages
pip install requests pandas
pip install "fastapi>=0.111" "pydantic>=2" # version constraints
Output:
Collecting requests
Downloading requests-2.32.3-py3-none-any.whl (64 kB)
Collecting pandas
Downloading pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.whl (13.0 MB)
Successfully installed certifi-2024.2.2 requests-2.32.3 pandas-2.2.2 ...
Freeze / requirements
# Save exact versions of all installed packages
pip freeze > requirements.txt
# Install from requirements
pip install -r requirements.txt
Output of pip freeze:
certifi==2024.2.2
charset-normalizer==3.3.2
idna==3.7
pandas==2.2.2
python-dateutil==2.9.0
requests==2.32.3
pip freezeincludes all transitive dependencies. For authoring a library or apyproject.toml-based project, prefer poetry or uv which maintain a separate lockfile.
Deactivate
deactivate
Output:
(prompt returns to normal — no (.venv) prefix)
Upgrade pip inside the venv
pip install --upgrade pip
Output:
Requirement already satisfied: pip in ./.venv/lib/python3.12/site-packages (24.0)
Successfully installed pip-24.3.1
Delete and recreate
rm -rf .venv
python -m venv .venv
Output: (none — exits 0 on success)
There are no lock files or metadata to clean up — just remove and recreate.
Quick reference table
| Task | Command |
|---|---|
| Create | python -m venv .venv |
| Activate (bash/zsh) | source .venv/bin/activate |
| Activate (fish) | source .venv/bin/activate.fish |
| Activate (PowerShell) | .venv\Scripts\Activate.ps1 |
| Install package | pip install <pkg> |
| List installed | pip list |
| Freeze to file | pip freeze > requirements.txt |
| Install from file | pip install -r requirements.txt |
| Deactivate | deactivate |
| Delete | rm -rf .venv |
Common pitfalls
Running without activation — if you
python script.pywithout activating, you'll use the system Python and won't see your installed packages. Checkwhich pythonbefore running anything.
PowerShell execution policy — if
.venv\Scripts\Activate.ps1fails with a policy error, run:Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Add
.venv/to your.gitignore. Never commit the virtual environment directory.
venv vs virtualenv vs uv venv vs conda
Python has several tools that create isolated environments. They serve overlapping needs with different tradeoffs in speed, language version flexibility, and ecosystem fit.
| Tool | Source | Speed | Python version mgmt | Best for |
|---|---|---|---|---|
python -m venv | stdlib (Python 3.3+) | Slow (~1s) | No — uses caller's Python | Default, no install needed |
virtualenv | third-party (pip install virtualenv) | Slow | No (pairs with pyenv) | Older Pythons, custom site-packages |
uv venv | Astral uv (Rust binary) | Fast (~50ms) | Yes (uv python install) | Modern projects, CI speed |
conda env | Anaconda / Miniconda / Miniforge | Slow | Yes (conda channels) | Data science, non-Python deps |
pyenv-virtualenv | pyenv plugin | Slow | Yes (pyenv-managed) | Per-version venvs |
pipenv | third-party | Slow | No | Legacy projects |
poetry env | poetry-managed | Slow | No | Poetry projects |
When to pick each:
python -m venv— zero install, always available, fine for most projects.virtualenv— slightly faster than stdlib venv on cold start, supports older Pythons, lets you set a custom prompt prefix and seed packages.uv venv— 20× faster, integrates withuv pip install, can download interpreters. Recommended for new projects.conda— only when you need non-Python binaries (CUDA toolkit, MKL, R, geo libraries) that aren't on PyPI.
How venv works under the hood
python -m venv creates a directory tree that looks like a miniature Python installation. Activating it places that tree's bin/ (or Scripts\ on Windows) on PATH first, so python and pip resolve to copies inside the venv instead of the system-wide install.
.venv/
├── bin/ (Linux/macOS) or Scripts/ (Windows)
│ ├── python → symlink to the base interpreter
│ ├── python3 → symlink
│ ├── pip
│ ├── pip3
│ ├── activate (bash/zsh)
│ ├── activate.fish
│ ├── activate.csh
│ └── Activate.ps1
├── include/ headers (mostly unused)
├── lib/
│ └── python3.12/
│ └── site-packages/ ← where installed packages live
└── pyvenv.cfg ← config file pointing at the base interpreter
Inspect the config:
cat .venv/pyvenv.cfg
Output:
home = /usr/bin
include-system-site-packages = false
version = 3.12.4
executable = /usr/bin/python3.12
command = /usr/bin/python3.12 -m venv /home/alice/myproject/.venv
Activation is just shell sugar around setting VIRTUAL_ENV, prepending .venv/bin to PATH, and updating the prompt. The Python interpreter inside the venv looks at pyvenv.cfg to find the base install for its standard library.
Activation by shell
Activation scripts ship in .venv/bin/ (Linux/macOS) and .venv\Scripts\ (Windows). Pick the file that matches your shell.
# bash / zsh — POSIX shells
source .venv/bin/activate
. .venv/bin/activate # same thing, shorter
# fish
source .venv/bin/activate.fish
# csh / tcsh
source .venv/bin/activate.csh
# nushell
overlay use .venv/bin/activate.nu
# PowerShell (Windows + cross-platform pwsh)
.venv\Scripts\Activate.ps1 # Windows
.venv/bin/Activate.ps1 # macOS/Linux pwsh
# cmd.exe
.venv\Scripts\activate.bat
# Git Bash on Windows
source .venv/Scripts/activate
Output: (none — exits 0 on success)
After activation, $VIRTUAL_ENV points to the venv directory and the prompt shows the venv name:
echo $VIRTUAL_ENV
Output:
/home/alice/myproject/.venv
python -m venv options
python -m venv accepts flags that control what's seeded inside the new environment.
python -m venv .venv # default
python -m venv --upgrade-deps .venv # upgrade pip/setuptools after creation
python -m venv --system-site-packages .venv # inherit system site-packages
python -m venv --without-pip .venv # skip pip install (rare)
python -m venv --copies .venv # copy files instead of symlinks
python -m venv --symlinks .venv # force symlinks (default on POSIX)
python -m venv --prompt myapp .venv # custom shell prompt prefix
python -m venv --clear .venv # delete contents first
python -m venv --upgrade .venv # upgrade venv to current Python
Output: (none — exits 0 on success)
| Flag | When to use |
|---|---|
--upgrade-deps | Always — ensures recent pip/setuptools |
--system-site-packages | Rare; mostly when leveraging a heavy system install (e.g., system PyTorch) |
--without-pip | Build pipelines where pip is added separately |
--copies | Filesystems that don't support symlinks (e.g., FAT, some network shares) |
--prompt | Cleaner prompt when the directory name is long |
--clear | Recreate without removing the directory itself |
--upgrade | After upgrading Python; relinks to the new interpreter |
--system-site-packages
By default a venv is fully isolated from the system Python's site-packages. With --system-site-packages the venv can read packages installed system-wide but still installs its own packages locally.
python -m venv --system-site-packages .venv
source .venv/bin/activate
python -c "import sys; print('\n'.join(sys.path))"
Output:
/home/alice/myproject/.venv/lib/python3.12/site-packages
/usr/lib/python3.12
/usr/lib/python3.12/lib-dynload
/usr/lib/python3/dist-packages ← system site-packages added back
--system-site-packagesdefeats most of the isolation venv is supposed to provide. Use it only when the system install includes something heavy and stable you don't want to duplicate.
Multiple Python versions
python -m venv always uses the interpreter that ran it. To create venvs with different versions, invoke the version explicitly.
python3.10 -m venv .venv-py310
python3.11 -m venv .venv-py311
python3.12 -m venv .venv-py312
# macOS Homebrew typical paths
/opt/homebrew/bin/python3.12 -m venv .venv
# pyenv-managed
~/.pyenv/versions/3.12.4/bin/python -m venv .venv
# uv-managed
uv venv --python 3.12 .venv-py312
Output: (none — exits 0 on success)
To recreate a venv with a different Python after the fact, delete and recreate — venv has no in-place version switch.
site-packages structure
site-packages is the directory each installed package lands in. Inspecting it is the fastest way to understand what's actually installed.
ls .venv/lib/python3.12/site-packages/
Output:
__pycache__/
_distutils_hack/
certifi/
certifi-2024.2.2.dist-info/
charset_normalizer/
charset_normalizer-3.3.2.dist-info/
idna/
idna-3.7.dist-info/
pip/
pip-24.1.dist-info/
requests/
requests-2.32.3.dist-info/
urllib3/
urllib3-2.2.1.dist-info/
Each installed package has:
- A package directory (
requests/) containing the importable Python modules. - A
.dist-info/metadata directory withMETADATA,RECORD,WHEEL,entry_points.txt, etc.
Find which file belongs to which package:
grep -l "^requests/" .venv/lib/python3.12/site-packages/*.dist-info/RECORD
Output:
.venv/lib/python3.12/site-packages/requests-2.32.3.dist-info/RECORD
ensurepip — bootstrapping pip
ensurepip is the stdlib module that installs pip into a Python environment. It is what python -m venv invokes internally and what you reach for when pip has been removed or never installed.
python -m ensurepip # install pip if missing
python -m ensurepip --upgrade # upgrade bundled pip
python -m ensurepip --default-pip # symlink `pip` to point at this Python's pip
Output:
Looking in links: /tmp/tmpxyz
Requirement already satisfied: setuptools in /home/alice/.venv/lib/python3.12/site-packages (69.5.1)
Requirement already satisfied: pip in /home/alice/.venv/lib/python3.12/site-packages (24.0)
On Debian/Ubuntu, the system Python ships without
ensurepipby default — installpython3-venvandpython3-pippackages first.
pip-tools workflow inside venv
pip-tools is the canonical way to maintain a pinned requirements.txt from a human-edited requirements.in. It's the closest equivalent to a uv.lock or poetry.lock when you must stay on plain pip + venv.
source .venv/bin/activate
pip install pip-tools
# requirements.in — direct deps only
cat > requirements.in <<EOF
fastapi
sqlalchemy>=2
pytest
EOF
pip-compile requirements.in # → requirements.txt (fully pinned)
pip-compile --upgrade requirements.in # refresh transitive deps
pip-compile --generate-hashes requirements.in # add SHA-256 hashes
pip-sync requirements.txt # make venv exactly match the file
Output of pip-compile:
#
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile requirements.in
#
fastapi==0.111.1
sqlalchemy==2.0.30
pytest==8.2.2
...
pip-sync is the missing piece pip lacks natively — it installs anything in requirements.txt, upgrades stale packages, and removes anything not listed.
Deactivation and cleanup
deactivate # exits the venv (back to system Python)
rm -rf .venv # delete the venv entirely
Output: (none — exits 0 on success)
deactivate is a shell function injected by the activation script. It restores PATH, removes VIRTUAL_ENV, and unsets the prompt prefix. There is no state to clean up — closing the shell achieves the same thing.
If deactivate is missing, the shell wasn't activated. If activation looks active but which python still shows the system Python, the activation script likely failed silently (most common on Windows PowerShell — see execution policy below).
.gitignore patterns
# .gitignore — venv directories
.venv/
venv/
env/
.env/
ENV/
env.bak/
venv.bak/
# pyenv-virtualenv local file
.python-version
# pip-tools artifacts (optional — usually kept)
# requirements.txt
Never commit a venv directory. They contain absolute paths in shebangs, are platform-specific, and can be hundreds of MB.
Devcontainer integration
A .devcontainer/devcontainer.json plus a postCreateCommand is the standard way to bootstrap a venv in VS Code or GitHub Codespaces.
{
"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bookworm",
"features": {},
"postCreateCommand": "python -m venv .venv && .venv/bin/pip install --upgrade pip && .venv/bin/pip install -r requirements.txt",
"customizations": {
"vscode": {
"settings": {
"python.defaultInterpreterPath": ".venv/bin/python",
"python.terminal.activateEnvironment": true
},
"extensions": ["ms-python.python", "charliermarsh.ruff"]
}
}
}
VS Code auto-discovers .venv next to pyproject.toml or requirements.txt and offers to use its interpreter — set python.defaultInterpreterPath to make that automatic.
Docker integration
Two patterns are common: a venv inside the image, or installing directly into the system Python of a slim base image.
Pattern 1 — venv inside the image (preferred)
FROM python:3.12-slim
# Create venv
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install deps
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy app
COPY . /app
WORKDIR /app
CMD ["python", "-m", "myapp"]
The venv at /opt/venv is isolated from the system Python — easy to copy to a smaller runtime image in a multi-stage build.
Pattern 2 — multi-stage with venv copy
FROM python:3.12 AS builder
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . /app
WORKDIR /app
CMD ["python", "-m", "myapp"]
The builder stage has compilers for native dependencies; the final image is much smaller.
Don't
source activateinside a Dockerfile — eachRUNis a separate shell and activation does not persist. Instead, prepend/opt/venv/bintoPATHonce viaENV.
Activation aliases
A common shell setup is to alias venv creation and activation to a single keystroke. Drop these in ~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish.
# bash/zsh — create and activate
mkvenv() {
python -m venv .venv --upgrade-deps --prompt "$(basename "$PWD")"
source .venv/bin/activate
}
# Auto-activate when entering a directory with .venv
chpwd() {
if [[ -f .venv/bin/activate ]]; then
source .venv/bin/activate
fi
}
Output: (none — exits 0 on success)
# fish
function mkvenv
python -m venv .venv --upgrade-deps --prompt (basename (pwd))
source .venv/bin/activate.fish
end
function __auto_venv --on-variable PWD
if test -f .venv/bin/activate.fish
source .venv/bin/activate.fish
end
end
Output: (none — exits 0 on success)
For a battle-tested version, install direnv and drop layout python into .envrc — it auto-activates .venv on cd and deactivates on leave.
Common pitfalls (additional)
Hardcoded shebangs break on rename — every script in
.venv/bin/has a#!/full/path/to/.venv/bin/pythonshebang. If you move the venv directory, scripts will fail. Recreate the venv instead of moving it.
System Python upgrades break the venv — if the OS upgrades Python 3.12.3 → 3.12.4 and removes 3.12.3's stdlib, your venv's symlinked interpreter may break. Recreate with
python -m venv --upgrade .venvafter major system updates.
Mixing venvs with sudo — never run
sudo pip installafter activating a venv.sudoresetsPATHand you'll install into the system Python instead of the venv, with root-owned files inside~/.local.
Use
--prompt myappso the prompt shows(myapp)instead of(.venv). When five terminals all have venvs active, named prompts save your sanity.
If
Activate.ps1fails with an execution policy error:Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser.
Real-world recipes
Recipe — fresh project with venv + pip-tools
mkdir myproject && cd myproject
python -m venv .venv --upgrade-deps --prompt myproject
source .venv/bin/activate
pip install pip-tools
cat > requirements.in <<EOF
fastapi
sqlalchemy>=2
EOF
pip-compile --generate-hashes requirements.in
pip-sync requirements.txt
echo ".venv/" >> .gitignore
Output: (none — exits 0 on success)
Recipe — share a venv between siblings
When two projects in a monorepo need the same dependencies, point both at one venv via pyvenv.cfg:
python -m venv ~/.venvs/shared
ln -s ~/.venvs/shared myproject/.venv
ln -s ~/.venvs/shared sibling-project/.venv
Output: (none — exits 0 on success)
A symlinked .venv keeps each project's directory layout clean while sharing the actual install — useful for slow-installing data-science stacks.
Recipe — recreate from scratch in CI
rm -rf .venv
python -m venv .venv --upgrade-deps
.venv/bin/pip install --no-cache-dir -r requirements.txt
.venv/bin/python -m pytest
Output:
Successfully installed pip-24.1 setuptools-69.5.1
Successfully installed fastapi-0.111.1 pydantic-2.7.4 sqlalchemy-2.0.30 ...
============================== 42 passed in 1.21s ==============================
--no-cache-dir avoids leaving wheel cache in CI workspaces that aren't cached anyway.
Recipe — venv inside WSL with VS Code on Windows
# Inside WSL Ubuntu
sudo apt update && sudo apt install -y python3.12 python3.12-venv
cd /mnt/c/Users/alice/code/myproject
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Output: (none — exits 0 on success)
Open the project in VS Code via code . from WSL; the Python extension picks up .venv/bin/python automatically.
Recipe — switch venvs with direnv
# .envrc in project root
layout python3.12
Output: (none — exits 0 on success)
direnv allow
# direnv auto-creates .direnv/python-3.12/ and activates on cd
Output:
direnv: loading ~/code/myproject/.envrc
direnv: export +VIRTUAL_ENV ~PATH
direnv replaces manual source .venv/bin/activate — entering the directory activates, leaving deactivates.
Quick reference (extended)
| Task | Command |
|---|---|
| Create with version pin | python3.12 -m venv .venv |
| Create with upgraded pip | python -m venv --upgrade-deps .venv |
| Create with custom prompt | python -m venv --prompt myapp .venv |
| Activate (bash/zsh) | source .venv/bin/activate |
| Activate (fish) | source .venv/bin/activate.fish |
| Activate (PowerShell) | .venv\Scripts\Activate.ps1 |
| Activate (cmd.exe) | .venv\Scripts\activate.bat |
| Show active venv | echo $VIRTUAL_ENV |
| Show interpreter path | which python / where.exe python |
| Install package | pip install <pkg> |
| Compile pinned reqs | pip-compile requirements.in |
| Sync to requirements | pip-sync requirements.txt |
| List installed | pip list |
| Deactivate | deactivate |
| Delete | rm -rf .venv (Remove-Item -Recurse .venv on PS) |
| Upgrade venv to current Python | python -m venv --upgrade .venv |