venv, pip, pyproject.toml, pipx, and uv — isolated environments, reproducible installs, and deployments that survive contact with a VPS. One venv per project, never
sudo pip, alwayspython -m pip.
- The Mental Model
- venv
- pip Essentials
- Requirements & Locking
- pyproject.toml
- Ubuntu / Debian Quirks
- pipx (CLI tools)
- uv (the fast consolidation)
- Common Workflows
- Tips & Gotchas
Interpreter → /usr/bin/python3 (system) — leave its packages alone
venv → Project-local copy of the interpreter + its own site-packages
pip → Installs INTO whichever environment its python belongs to
pyproject.toml → Declares what your project needs (the intent)
requirements.txt → Pins exactly what got installed (the lockfile)
pipx → venvs-as-a-service for CLI tools (ruff, httpie, ...)
sudo apt install python3-venv # Ubuntu ships venv separately
python3 -m venv .venv # Create (convention: .venv in project root)
python3 -m venv --upgrade-deps .venv # Create with pip/setuptools upgraded
source .venv/bin/activate # Activate (prompt shows (.venv))
deactivate # Leave.venv/bin/python script.py # Runs with the venv's packages
.venv/bin/pip install httpx # Installs into the venv
# This is how systemd units and cron jobs should call Python:
# ExecStart=/opt/agent/.venv/bin/python main.py (see systemd sheet)which python # Should show .venv/bin/python when active
python -c "import sys; print(sys.prefix)"
pip --version # Shows which site-packages it targetsrm -rf .venv
python3 -m venv .venv && .venv/bin/pip install -r requirements.txtecho ".venv/" >> .gitignore # Never commit a venvpython -m pip install httpx # `python -m pip` — always targets THIS python
pip install "httpx==0.27.0" # Exact version
pip install "httpx>=0.27,<1.0" # Range
pip install "pydantic~=2.7.0" # Compatible release (>=2.7.0, <2.8)
pip install --upgrade httpx # Upgrade one package
pip uninstall httpxpip install -e . # Editable install of current project (needs pyproject.toml)
pip install -e ".[dev]" # Editable + optional dependency group
pip install "git+https://github.com/user/repo.git@main" # Straight from gitpip list # Installed packages
pip list --outdated # What has updates
pip show httpx # Version, deps, install location
pip check # Detect broken/conflicting dependencies
pip cache purge # Reclaim disk from the wheel cachepip install -r requirements.txt # Install pinned set
pip freeze > requirements.txt # Snapshot EVERYTHING (see caveat below)⚠️ pip freeze pins your deps AND their deps AND pip's plumbing, with no
record of what you actually asked for. Fine for quick snapshots; for
maintained projects, declare intent in pyproject.toml and compile a lock:
pip install pip-tools
pip-compile pyproject.toml -o requirements.txt # Resolve + pin from [project.dependencies]
pip-compile --upgrade -o requirements.txt pyproject.toml # Refresh all pins
pip-compile --upgrade-package httpx -o requirements.txt pyproject.toml # Bump ONE dep
pip-sync requirements.txt # Make env match the file EXACTLY (removes strays)pip install -r requirements.txt -c constraints.txtOne file for project metadata, dependencies, and tool config — replaces setup.py, setup.cfg, requirements.in, and scattered dotfiles.
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "myagent"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"httpx>=0.27",
"pydantic>=2.0,<3",
]
[project.optional-dependencies]
dev = ["pytest", "ruff"]
[project.scripts]
myagent = "myagent.main:run" # `myagent` CLI command after install
[tool.ruff]
line-length = 100
[tool.pytest.ini_options]
testpaths = ["tests"]pip install -e ".[dev]" # Install project + dev extras, editableDivision of labor:
pyproject.toml → committed, human-edited, loose ranges (intent)
requirements.txt → committed, machine-generated, exact pins (deploy artifact)
sudo apt install python3-venv python3-pip python3-dev build-essential
# python3-dev + build-essential: needed when pip compiles C extensions
# ("error: Python.h: No such file" → this is the fix)⚠️ NEVER `sudo pip install` — it writes into apt's territory and breaks
system tooling. The system interpreter belongs to apt; your code gets a venv.
Ubuntu 23.04+/Debian 12+ block bare `pip install` outside a venv by design.
Correct fixes: use a venv (projects) or pipx (CLI tools).
Escape hatch (understand what you're doing): pip install --break-system-packages
Ubuntu 22.04 doesn't enforce this yet — but behave as if it does.
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt install python3.12 python3.12-venv
python3.12 -m venv .venv # venv pinned to that interpreterEach tool gets its own hidden venv; the command lands on your PATH. For tools you run, not libraries you import.
sudo apt install pipx
pipx ensurepath # Put ~/.local/bin on PATH (new shell after)
pipx install ruff # Install a CLI tool
pipx install --python python3.12 tool # Pin the interpreter
pipx list
pipx upgrade ruff
pipx upgrade-all
pipx uninstall ruff
pipx run cowsay -t "no install" # Ephemeral: run without installing
pipx inject ipython rich # Add a lib INTO a pipx tool's venvOne Rust binary replacing pip + venv + pip-tools + pipx + pyenv, 10–100x faster resolves. Same concepts, fewer moving parts.
curl -LsSf https://astral.sh/uv/install.sh | shuv venv # Create .venv (fast)
uv pip install -r requirements.txt # pip, but fast
uv pip compile pyproject.toml -o requirements.txt # pip-compile, but fast
uv pip sync requirements.txt # pip-sync equivalent
uv tool install ruff # pipx equivalent
uv python install 3.12 # Managed interpreter (no PPA needed)uv init myproject # Scaffold pyproject.toml
uv add httpx # Add dep + update lockfile
uv add --dev pytest # Dev dependency
uv lock # Write uv.lock
uv sync # Make .venv match the lockfile
uv run python main.py # Run inside the env, no activation
uv run pytest💡 Adoption path: start with the drop-in commands against your existing
requirements files — zero workflow change, immediate speed. Move to
project mode (uv.lock) per-repo when ready.
mkdir myagent && cd myagent
python3 -m venv .venv && source .venv/bin/activate
# write pyproject.toml ([project] block above)
pip install -e ".[dev]"
pip-compile pyproject.toml -o requirements.txt
git init && echo ".venv/" >> .gitignoregit clone git@github:CryptoAI-Jedi/myagent.git /opt/agent
cd /opt/agent
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt # Exact pins — same env as tested
# systemd unit: ExecStart=/opt/agent/.venv/bin/python main.py
sudo systemctl enable --now myagentpip-compile --upgrade-package httpx -o requirements.txt pyproject.toml
pip-sync requirements.txt
pytest # Verify before it ships
git diff requirements.txt # Review exactly what movedCOPY requirements.txt first → RUN pip install → COPY . .
The lockfile IS the cache key: code edits stop reinstalling the world.
- "pip installed it but import fails" — pip and python pointed at different environments. Cure:
python -m pip install ...always; diagnose withwhich python; pip --version. - venvs don't survive being moved or renamed — activation scripts and shebangs hardcode absolute paths. Renamed the project dir?
rm -rf .venvand recreate; it takes seconds. - systemd/cron don't "activate" — activation just edits PATH for interactive shells. Non-interactive contexts use the absolute path:
/opt/agent/.venv/bin/python. - Build failures on a slim VPS (
Python.h missing,gcc not found) —sudo apt install python3-dev build-essential; the wheel for your platform didn't exist so pip fell back to compiling. pip freezeafter months of experiments — your "lockfile" now includes every abandoned experiment.pip-sync/uv pip syncexist precisely to remove what the file doesn't list.~=beats>=for apps you deploy —pydantic~=2.7.0accepts patch fixes, refuses surprise minor jumps. Loose ranges belong in libraries, not deploy targets.- Venv Python version is frozen at creation — apt upgrading python3 can break existing venvs (missing modules, segfaults). Recreate venvs after interpreter upgrades.
pipx ensurepathneeds a new shell — the tool "isn't found" until PATH reloads;exec $SHELLor open a new terminal.- Quote extras in zsh —
pip install -e ".[dev]"; unquoted square brackets are glob syntax and fail confusingly.
Last Updated: 2026-07