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 withpip-compile(pip-tools) oruv 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(orpython3.X-venvmatching 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.