A virtual environment is a self-contained directory with its own Python interpreter link and its own installed packages. Each project gets exactly the dependencies it needs, without conflicts with other projects — or with the operating system’s Python. On a server it’s not optional: modern Ubuntu and Debian refuse system-wide pip install for good reason.

Create one

sudo apt install -y python3-venv      # Debian/Ubuntu: the venv module is a separate package
cd ~/myapp
python3 -m venv .venv

This creates .venv/ containing bin/python, bin/pip and an empty site-packages. The name .venv is a common convention that most tools (VS Code, uv, Poetry) recognise automatically. Add it to .gitignore.

Use a specific Python version by calling that interpreter: python3.13 -m venv .venv.

Activate and deactivate

source .venv/bin/activate
(.venv) $ which python
/home/alice/myapp/.venv/bin/python
(.venv) $ deactivate

Activation only adjusts your shell’s PATH so python and pip point into the environment. You don’t have to activate: calling .venv/bin/python or .venv/bin/pip directly works the same — which is exactly what you do in scripts and services.

Install packages

source .venv/bin/activate
pip install --upgrade pip
pip install requests flask
pip install -r requirements.txt

Packages land in .venv/lib/python3.X/site-packages/, nowhere else.

Record dependencies

The simplest approach:

pip freeze > requirements.txt

pip freeze lists every installed package with exact versions, including indirect dependencies. That’s reproducible but hard to maintain. A cleaner pattern:

  • requirements.in — only your direct dependencies (flask, sqlalchemy>=2).
  • requirements.txt — the fully pinned result, generated with pip-compile (pip-tools) or uv pip compile requirements.in -o requirements.txt.

Modern projects often use pyproject.toml with a lock file managed by uv or Poetry.

Recreate instead of repairing

Virtual environments are disposable. If one breaks — or you upgrade the system Python — delete and rebuild it:

rm -rf .venv
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

Don’t copy a venv to another path or server: it contains absolute paths and links to the interpreter it was created with.

Using a venv in production

systemd service

[Unit]
Description=My Flask app
After=network.target

[Service]
User=app
WorkingDirectory=/home/app/myapp
ExecStart=/home/app/myapp/.venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 app:app
Restart=always
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target

No activation needed — the absolute path to the venv’s binary is enough. See systemd service for a Python app.

Cron

*/10 * * * * /home/app/myapp/.venv/bin/python /home/app/myapp/tasks/sync.py >> /home/app/sync.log 2>&1

Shebang

A script can point at the venv interpreter directly:

#!/home/app/myapp/.venv/bin/python

Command-line tools: pipx

For Python applications you want available everywhere (like httpie, ansible, black), use pipx: it gives each tool its own hidden venv and puts the command on your PATH.

sudo apt install -y pipx
pipx ensurepath
pipx install httpie

Faster alternative: uv

uv creates environments and installs packages much faster than venv + pip, with a compatible interface:

uv venv
uv pip install -r requirements.txt
uv run python app.py       # runs inside the project's environment

System site packages (rarely)

python3 -m venv --system-site-packages .venv lets the environment see packages installed by apt (python3-numpy, for example). Useful in narrow cases such as libraries with complex system dependencies; otherwise keep environments isolated.

Troubleshooting

  • The virtual environment was not created successfully because ensurepip is not available — sudo apt install python3-venv (or python3.X-venv matching the interpreter).
  • error: externally-managed-environment — you ran pip outside a venv; activate it or use .venv/bin/pip. See the full explanation.
  • Wrong Python inside the venv — it uses the interpreter that created it. Recreate with the version you want.
  • Packages “missing” in a service — the service runs a different interpreter; use the venv’s absolute path.
  • Building a package fails (Python.h: No such file) — sudo apt install python3-dev build-essential.

Next steps: get the right interpreter with installing Python on Ubuntu, then deploy with Django + Gunicorn + Nginx.

  • Python
  • venv
  • Development