Key Takeaways
- Virtual env before everything:
python3 -m venv .venv→source .venv/bin/activate→ install packages. Never install globally. - uv replaces pip + venv + pyenv: One tool for environments, packages, Python version management.
pip install uvonce, thenuvfor everything. externally-managed-environmenterror: Ubuntu 24.04 blocks system-wide pip installs. Use venv or add--break-system-packagesonly for CLI tools.- pyproject.toml is the standard: Replace requirements.txt with
[project] dependencies = [...]in pyproject.toml.
Introduction
Direct Answer: How do I set up Python 3.12 with virtual environments and package management on Ubuntu 24.04 in 2026?
Python 3.12 is pre-installed on Ubuntu 24.04. Create a project directory and virtual environment: mkdir myproject && cd myproject && python3 -m venv .venv && source .venv/bin/activate. Your shell prompt changes to (.venv) confirming activation. Install packages: pip install requests fastapi uvicorn. Save dependencies: pip freeze > requirements.txt. Deactivate: deactivate. For the modern approach, use uv: pip install uv --break-system-packages && uv init myproject && cd myproject && uv add fastapi uvicorn && uv run python main.py. The uv tool creates the venv, manages Python versions, and installs packages 10–100× faster than pip with lockfile support.
Part 1: Python Installation
# Python 3.12 is pre-installed on Ubuntu 24.04
python3 --version
python3 -m pip --version
which python3
Expected output:
Python 3.12.3
pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)
/usr/bin/python3
# Install additional Python tools
sudo apt-get install -y python3-venv python3-dev python3-pip
# Install uv (the modern pip replacement — written in Rust, 10-100x faster)
pip install uv --break-system-packages
uv --version
Expected output: uv 0.5.4
Part 2: Virtual Environments
# Create a project with venv
mkdir ~/myproject && cd ~/myproject
python3 -m venv .venv
# Activate (Linux/macOS)
source .venv/bin/activate
# Your prompt changes to: (.venv) ubuntu@server:~/myproject$
# Install packages (into the venv, NOT system-wide)
pip install fastapi uvicorn pydantic httpx
# Check what's installed
pip list
# Save exact versions
pip freeze > requirements.txt
cat requirements.txt | head -5
# Deactivate when done
deactivate
Expected output:
Package Version
---------- -------
fastapi 0.115.6
uvicorn 0.32.1
pydantic 2.10.0
httpx 0.28.0
Part 3: Modern Project Setup with uv
# uv init creates a complete project structure
uv init myapp
cd myapp
ls -la
Expected output:
.python-version ← Python version pin (3.12)
pyproject.toml ← Project config + dependencies
README.md
src/
myapp/
__init__.py
main.py
# Add dependencies (creates virtual env automatically)
uv add fastapi "uvicorn[standard]" pydantic sqlalchemy
# Run the app
uv run python src/myapp/main.py
# Add a dev dependency (not included in production installs)
uv add --dev pytest pytest-asyncio httpx
# Sync environment from lockfile (reproducible installs)
uv sync # Installs exact versions from uv.lock
# Update all packages
uv lock --upgrade
Part 4: pyproject.toml
# pyproject.toml — the standard project file in 2026
[project]
name = "myapp"
version = "0.1.0"
description = "A sovereign web application"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115",
"uvicorn[standard]>=0.32",
"pydantic>=2.10",
]
[dependency-groups]
dev = [
"pytest>=8.0",
"httpx>=0.28", # For FastAPI test client
"ruff>=0.8", # Linter + formatter
]
[tool.ruff]
line-length = 100
select = ["E", "F", "I"] # Errors, pyflakes, isort
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
Part 5: Essential Python Patterns
# Type hints (standard in 2026 Python)
def greet(name: str, count: int = 1) -> list[str]:
return [f"Hello, {name}!" for _ in range(count)]
# Dataclasses (built-in structured data)
from dataclasses import dataclass, field
@dataclass
class Config:
host: str = "localhost"
port: int = 8000
debug: bool = False
tags: list[str] = field(default_factory=list)
cfg = Config(host="0.0.0.0", debug=True)
print(cfg) # Config(host='0.0.0.0', port=8000, debug=True, tags=[])
# pathlib for file operations (never use os.path in 2026)
from pathlib import Path
config_path = Path.home() / ".config" / "myapp" / "config.json"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('{"version": "1.0"}')
print(config_path.read_text())
# f-strings (clear formatting)
name, score = "Alice", 98.5
print(f"{name}: {score:.1f}% ({score:.0f}/100)") # Alice: 98.5% (99/100)
Conclusion
Python 3.12 on Ubuntu 24.04 is ready: virtual environments prevent global pollution, uv replaces pip with 10–100× faster installs and lockfile reproducibility, and pyproject.toml is the unified project configuration format. These foundations underpin every Python guide in the Dev Corner — FastAPI, automation, AI integrations, and testing.
People Also Ask
Should I use venv or uv for Python virtual environments?
Both create isolated Python environments, but uv is the better choice for new projects in 2026. uv manages Python versions (no pyenv needed), creates environments automatically when you run uv add, produces a lockfile (uv.lock) for reproducible installs, and is 10–100× faster than pip for installing packages. Use venv only if uv isn’t available or if you’re working with legacy projects that predate uv. The resulting virtual environment is identical — uv just creates and manages it more conveniently.
Part 6: Project Structure and Package Layout
A clean Python project layout is the foundation of maintainable code. In 2026, the recommended structure looks like this:
myapp/
.venv/
src/
myapp/
__init__.py
main.py
config.py
tests/
test_main.py
pyproject.toml
README.md
LICENSE
.gitignore
uv.lock
Put application code under src/ to avoid accidental imports from the repository root. Keep tests in a parallel tests/ folder and use pytest naming conventions such as test_*.py.
A minimal src/myapp/main.py might look like:
from pathlib import Path
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root() -> dict[str, str]:
return {"message": "Hello, sovereign Python 2026"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
This separation keeps the codebase tidy and makes packaging easier when you publish wheels or deploy containers.
Part 7: Dependency Management and Reproducible Installs
uv is now the standard dependency manager for Python projects in 2026. It simplifies the workflow by combining virtual environment creation, package installation, and lockfile management.
Initialize a new project with uv init:
uv init myapp
cd myapp
uv add fastapi "uvicorn[standard]" pydantic
uv add --dev pytest ruff
uv sync
uv creates uv.lock, which pins exact package versions for reproducible installs. Store pyproject.toml and uv.lock in Git, but do not store the virtual environment itself.
If you still use pip, use pip install --requirement requirements.txt and pip freeze > requirements.txt only inside a venv or uv environment. Never run sudo pip install on Ubuntu 24.04 unless you understand --break-system-packages and the risks.
When you need a specific Python version, uv can manage that too:
uv env use 3.12
uv env list
This ensures every developer and CI runner uses the same interpreter version.
Part 8: Linters, Formatters, and Static Analysis
Clean code is secure code. In 2026, use a standard toolchain of ruff, black, mypy or pyright, and isort.
Add these tools to your pyproject.toml:
[tool.ruff]
line-length = 100
select = ["E", "F", "I", "B", "C"]
exclude = ["tests"]
[tool.isort]
profile = "black"
[tool.pyright]
pythonVersion = "3.12"
Use uv run to execute them inside the project environment:
uv run ruff check src tests
uv run ruff format src tests
uv run python -m pytest
Static analysis catches common bugs before they reach production. In particular, mypy and ruff can catch type mismatches, unused imports, and formatting issues that frequently lead to runtime problems.
Part 9: Testing and Continuous Verification
A sovereign Python project needs a test strategy that is fast, reliable, and automated.
Create tests with pytest and httpx for web services:
from httpx import AsyncClient
from myapp.main import app
async def test_read_root() -> None:
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello, sovereign Python 2026"}
Run tests with coverage in CI:
uv run pytest --cov=src/myapp --cov-report=term-missing
Use pre-commit if you want local guards before commits, but uv makes the core dependency story easier.
Add test utilities in tests/conftest.py for shared fixtures and isolated environments, and keep the suite fast enough to run on every push.
Part 10: Packaging, Builds, and Distribution
When your Python app is ready for deployment, use pyproject.toml to build wheels and source distributions.
uv run python -m build
This creates dist/myapp-0.1.0-py3-none-any.whl and dist/myapp-0.1.0.tar.gz.
Prefer wheels for deployment because they install faster and are reproducible. For local deployment or container builds, copy the wheel into the image and install it with pip install dist/myapp-0.1.0-py3-none-any.whl.
If you publish to a private package index, use a local Artifactory, Nexus, or private PyPI-compatible registry rather than public PyPI. Keep the package index and authentication under your sovereign control.
Part 11: Security and Environment Isolation
A safe Python environment is one that is isolated, pinned, and audited.
- Never install dependencies globally on Ubuntu 24.04 unless the package is a one-off CLI tool and you understand the system package policy.
- Use
uvorvenvfor every project. - Use
pip-auditorpython -m pip auditto scan dependencies for vulnerabilities. - Pin exact versions in
uv.lock, and review lockfile changes in code review.
For CLI utilities or worker scripts, prefer separate environments per service. This avoids dependency conflict drift and keeps each service reproducible.
Part 12: Python 3.12 Features for Sovereign Developers
Python 3.12 brings improvements that matter for modern applications:
typingimprovements likeSelfandTypeAliasfor cleaner type definitions.- Faster startup and smaller memory usage in many workloads.
- Better error messages and exception notes.
tomllibfor reading TOML configuration directly from the standard library.
Example using tomllib:
from pathlib import Path
import tomllib
config_path = Path("pyproject.toml")
config = tomllib.loads(config_path.read_text())
print(config["project"]["dependencies"])
These language improvements make Python code easier to read and diagnose, especially in a sovereign environment where clarity is essential.
Part 13: Performance and Concurrency
Python 3.12 is a strong choice for I/O-bound services and automation. Use asynchronous frameworks like FastAPI and asyncio for local APIs, and prefer worker pools for CPU-bound tasks.
Example asynchronous file scanning:
import asyncio
from pathlib import Path
async def read_file(path: Path) -> str:
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, path.read_text)
async def main() -> None:
files = list(Path(".").glob("**/*.py"))
contents = await asyncio.gather(*(read_file(path) for path in files))
print(f"Read {len(contents)} files")
asyncio.run(main())
For compute-heavy tasks, use process-based concurrency:
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as executor:
results = executor.map(compute_heavy_function, data)
This keeps CPU-bound work off the event loop and avoids the Global Interpreter Lock limitations.
Part 14: Local AI and Data Workflows
Python remains the dominant language for local AI and data tooling. In 2026, use Python to integrate with local-model runtimes such as Ollama, OpenAI-compatible APIs, or embedded inference libraries.
Example local AI call:
from httpx import AsyncClient
async def generate(prompt: str) -> str:
async with AsyncClient(base_url="http://127.0.0.1:11434") as client:
response = await client.post("/v1/completions", json={
"model": "llama-3-13b",
"prompt": prompt,
})
return response.json()["choices"][0]["text"]
For data processing, prefer built-in libraries such as csv, json, sqlite3, and pathlib before reaching for heavier frameworks. This keeps your applications light, portable, and easier to audit.
Part 15: Deployment Patterns for Python 2026
Deploy Python services in containers or virtual machines with a fixed environment. A simple container workflow looks like this:
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install build
RUN pip install .
COPY src ./src
CMD ["uvicorn", "myapp.main:app", "--host", "0.0.0.0", "--port", "8000"]
Use uv or pip inside the container to install pinned dependencies. Store the built image in a private registry and sign it with Cosign if you are using a sovereign supply chain model.
A local VM deployment can use uv run in a systemd service:
[Unit]
Description=MyApp service
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/srv/myapp
ExecStart=/srv/myapp/.venv/bin/uvicorn myapp.main:app --host 127.0.0.1 --port 8000
Restart=on-failure
[Install]
WantedBy=multi-user.target
This pattern keeps the Python app isolated and manageable on Ubuntu 24.04.
Part 16: Command Line Scripts and Automation
Python excels at local automation. Build CLI tools with argparse, typer, or click, and keep them inside your src package.
Example typer command:
import typer
from pathlib import Path
app = typer.Typer()
@app.command()
def init(path: Path = Path('.')) -> None:
"""Create a project scaffold."""
(path / 'src').mkdir(parents=True, exist_ok=True)
typer.echo(f"Project initialized at {path}")
if __name__ == '__main__':
app()
Package CLI entry points in pyproject.toml:
[project.scripts]
myapp = "myapp.main:app"
This makes your scripts importable and testable, which is superior to ad hoc shell scripts.
Part 17: Documentation and README Standards
A sovereign Python project should document the setup, environment, testing, and deployment workflows clearly.
Your README.md should include:
- Python version and install instructions
- virtual environment activation steps
- package installation commands
- how to run tests
- how to start the application
- where to find configuration files
Example section:
## Local Development
1. `uv init myapp`
2. `uv add fastapi uvicorn`
3. `uv run python -m uvicorn src.myapp.main:app --reload`
Also include a CONTRIBUTING.md for coding standards and a SECURITY.md for responsible disclosure processes. Documentation is part of the sovereign codebase and should be version-controlled.
Part 18: Caching and Offline Dependency Installation
In an air-gapped or local-first environment, cached dependencies are invaluable.
Use uv sync with a local cache directory, and mirror packages when possible.
uv config set cache-dir /var/cache/uv
uv add fastapi
uv sync
For full offline installs, pre-populate the cache on a connected machine, then transfer the cache bundle into the air-gapped environment. This lets you install packages without direct network access while preserving exact versions.
Part 19: Packaging Wheels for Local Deployment
Wheels are the preferred binary artifact for Python deployment. Build them locally and store them in a private registry or artifact repository.
uv run python -m build
Then install from the wheel:
pip install dist/myapp-0.1.0-py3-none-any.whl
Avoid deploying via pip install . in production unless you have a controlled build pipeline. Wheels provide a reproducible, audited package artifact that can be signed and verified.
Part 20: Local AI Prototyping with Python
Python is the lingua franca of local AI development. Use minimal dependencies and local runtimes to keep models sovereign.
Example using ONNX Runtime or a local LLM inference API:
from pathlib import Path
MODEL_PATH = Path('/opt/models/llama-3-13b.onnx')
if MODEL_PATH.exists():
print('Model ready for inference')
When integrating with local AI, prefer stable APIs and do not hard-code remote endpoints. That ensures your codebase remains portable even if the model runtime moves between hosts.
Part 21: Data Processing and File IO
For local data workflows, use standard libraries instead of heavy dependencies when possible.
from pathlib import Path
import csv
csv_path = Path('data.csv')
with csv_path.open(newline='') as file:
reader = csv.DictReader(file)
for row in reader:
print(row['id'], row['name'])
Use sqlite3 for small local datasets and json or tomllib for configuration. These built-in tools are easier to audit and maintain than complex external stacks.
Part 22: Secure Python Dependencies
Python dependencies carry risk. Use pip-audit or uv audit if available to scan packages before installation.
uv run python -m pip audit
Review dependency graphs and avoid unneeded transitive packages. A smaller dependency footprint reduces your attack surface.
When a dependency is vulnerable, patch it immediately in your lockfile and rebuild the environment. Do not ignore security warnings in pip-audit or similar scans.
Part 23: Production Readiness and Process Supervision
For services deployed on Ubuntu 24.04, use systemd to supervise Python applications. A typical unit file looks like this:
[Unit]
Description=MyApp service
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/srv/myapp
ExecStart=/srv/myapp/.venv/bin/uvicorn myapp.main:app --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
This keeps your Python service managed by the OS and restarts it if it crashes. Combine it with log rotation and health checks for a resilient deployment.
Part 24: Local Testing with Docker
If you use containers for local testing, build a small image and mount source code for rapid iteration.
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install build
RUN pip install .
COPY src ./src
CMD ["uvicorn", "myapp.main:app", "--host", "0.0.0.0", "--port", "8000"]
Use docker build -t myapp:local . and docker run -p 8000:8000 myapp:local. Keep the image minimal and use the same dependency pinning as your non-containerized workflows.
Part 25: Long-Term Maintenance and Upgrade Strategy
A sovereign Python codebase should have an upgrade plan. Track Python release notes and schedule upgrades at least once per year.
Keep tests green, update pyproject.toml with supported Python versions, and use uv to create new environments for the upgraded interpreter. Do not let the codebase drift onto unsupported Python versions.
Part 26: CI/CD for Python Projects
Your Python setup should integrate with CI. Use a simple pipeline that validates the environment on every push.
A typical GitHub Actions workflow:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snok/install-uv@v1
- run: uv sync
- run: uv run ruff check src tests
- run: uv run python -m pytest
The CI job should use the same uv.lock file as local development, ensuring parity between developer machines and automated builds.
Part 27: VS Code and Devcontainer Configuration
If your team uses VS Code, provide a devcontainer.json for a reproducible development environment.
Example devcontainer.json:
{
"name": "Python 3.12",
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"postCreateCommand": "uv sync",
"customizations": {
"vscode": {
"extensions": ["ms-python.python", "charliermarsh.ruff"]
}
}
}
This gives every developer the same tools, Python version, and dependency setup. It also makes onboarding faster and more sovereign because the configuration is owned by your repo.
Part 28: Profiling and Performance Investigation
Python performance matters for production services. Use built-in profiling tools such as cProfile, yappi, or scalene.
Example cProfile usage:
uv run python -m cProfile -o profile.out src/myapp/main.py
uv run python -m pstats profile.out
Monitor CPU-bound functions, I/O wait, and event loop latency. A sovereign codebase should include perf guidance in the developer documentation so operators know where to look when a service slows down.
Part 29: Python SBOM and Dependency Evidence
Generate an SBOM for your Python project to support supply chain visibility.
Use syft or pip-licenses to capture dependency metadata.
syft dir:. -o spdx-json > python-sbom.json
Store the SBOM with your release artifacts. This makes it easier to verify exactly what packages were installed when a particular version was deployed.
Part 30: Data Security in Python Applications
Protect sensitive data in Python apps by default. Use environment-based configuration and avoid logging secrets.
A secure config loader might look like this:
from pydantic import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()
Do not commit .env files to Git. Use role-based access on the host file system and, when possible, load secrets from a local vault or encrypted store.
Part 31: Building Maintainable Python Libraries
If your project is a library rather than an application, follow library best practices:
- use
src/layout - provide a
READMEwith examples - include a changelog
- write public API docs
- version using semantic versioning
A library package should also include a pyproject.toml with project.urls for documentation and source repository.
Part 32: Local Python Security Practices
Python security is an ongoing process. In addition to dependency audits, add these practices:
- run
uv run ruff checkbefore commits - avoid
eval()and insecure deserialization - escape user input before using it in shell commands
- validate all HTTP request payloads in web services
- use
httpxwith TLS verification enabled
These practices reduce the risk of application-layer vulnerabilities in local Python services.
Part 33: Local Dev Environment Sanity Checks
A good Python development environment includes sanity checks on every startup. Add a small script that verifies the active Python version, checks for a virtual environment, and confirms the lockfile is in sync.
For example:
import sys
from pathlib import Path
if sys.version_info[:2] != (3, 12):
raise SystemExit("Python 3.12 is required")
if Path('.venv').exists():
print('Virtual environment detected')
else:
raise SystemExit('Activate .venv before running this project')
This lightweight guard keeps developers aligned and prevents accidental sudo pip install mistakes.
Part 34: Local Developer Onboarding Checklist
Every Python project benefits from an onboarding checklist. Include steps for:
- cloning the repository
- installing
uv - creating and activating the environment
- syncing dependencies
- running linting and tests
- starting the application locally
A checklist reduces friction for new contributors and ensures everyone follows the same sovereign setup path.
Part 35: Release Readiness and Post-Release Review
A sovereign Python project should include a release checklist that validates the application before it goes live. Key steps include:
- running unit and integration tests
- verifying dependencies with
uv lock - checking formatting and linting
- building a wheel and confirming it installs cleanly
- generating documentation or README artifacts
After release, review deployment telemetry and test logs to confirm behavior. A post-release review turns each deploy into an opportunity for continuous improvement.
Further Reading
- Build a REST API with FastAPI — put this Python setup to immediate use
- Python for DevOps Automation — automation scripts using these foundations
- Python + Ollama: Build Local AI Apps — AI integration with Python
Tested on: Ubuntu 24.04 LTS. Python 3.12.3, uv 0.5.4. Last verified: April 29, 2026.