Intermediate
12 min read
#Python#Packaging#Docker#Deployment#pyproject.toml

Packaging & Deployment — The Complete Notebook

Comprehensive guide on Packaging & Deployment — The Complete Notebook.

Packaging & Deployment

1. Overview#

Writing working code is only part of the job — it also needs to be packaged in a way others can install, and deployed somewhere it can actually run reliably. This note covers modern Python packaging (pyproject.toml), containerizing an app with Docker, and the basics of getting a Python service into production.


2. Modern Packaging with pyproject.toml#

pyproject.toml is the modern, standardized way to define a Python project's metadata and dependencies — replacing the older setup.py/requirements.txt-only approach.

2.1 A Basic pyproject.toml#

toml
[project] name = "my-api" version = "1.0.0" description = "A FastAPI service for order management" requires-python = ">=3.11" dependencies = [ "fastapi>=0.111.0", "uvicorn[standard]>=0.30.0", "sqlalchemy>=2.0", "pydantic>=2.0", ] [project.optional-dependencies] dev = [ "pytest>=8.0", "mypy>=1.10", "ruff>=0.5", ] [build-system] requires = ["setuptools>=68.0"] build-backend = "setuptools.build_meta"

2.2 Installing From It#

bash
pip install . # install the project itself pip install ".[dev]" # install with the optional "dev" dependency group pip install -e . # editable install — for actively developing the package

2.3 Project Layout#

code
my-api/ ├── pyproject.toml ├── README.md ├── .gitignore ├── src/ │ └── my_api/ │ ├── __init__.py │ └── main.py └── tests/ └── test_main.py

The src/ layout (code inside src/my_api/ rather than directly at the project root) is now widely recommended — it prevents accidentally importing your package from the wrong location during testing.


3. Environment & Dependency Management Tools#

ToolWhat It Adds Over Plain pip
venv + pipBuilt-in, minimal, works everywhere
poetryDependency resolution, lock files, publishing, all via pyproject.toml
uvExtremely fast installs/resolution, drop-in pip/venv replacement
pipenvCombines pip + virtualenv management with a Pipfile.lock
bash
# Example with poetry poetry init # interactive pyproject.toml setup poetry add fastapi # adds and locks a dependency poetry install # installs everything from the lock file poetry run uvicorn my_api.main:app

4. Environment Variables & Configuration#

4.1 .env Files for Local Development#

Mathematical Formulation
# .env
DATABASE_URL=postgresql://user:pass@localhost/mydb
API_KEY=dev-secret-key
DEBUG=true
🐍 Python
import os from dotenv import load_dotenv load_dotenv() DATABASE_URL = os.environ["DATABASE_URL"]

Never commit .env to version control — add it to .gitignore. In production, environment variables are typically injected by the hosting platform (Docker, Kubernetes secrets, cloud provider config) rather than read from a file.

4.2 Typed Settings with Pydantic#

🐍 Python
from pydantic_settings import BaseSettings class Settings(BaseSettings): database_url: str api_key: str debug: bool = False class Config: env_file = ".env" settings = Settings() # automatically reads and validates from environment/.env

5. Containerizing with Docker#

5.1 A Basic Dockerfile for a FastAPI App#

dockerfile
FROM python:3.12-slim WORKDIR /app # Copy dependency files first — leverages Docker layer caching COPY pyproject.toml . RUN pip install --no-cache-dir . # Copy the rest of the application code COPY src/ ./src/ EXPOSE 8000 CMD ["uvicorn", "src.my_api.main:app", "--host", "0.0.0.0", "--port", "8000"]

5.2 Building & Running#

bash
docker build -t my-api:latest . docker run -p 8000:8000 --env-file .env my-api:latest

5.3 docker-compose for Multi-Service Apps#

yaml
# docker-compose.yml services: api: build: . ports: - "8000:8000" env_file: - .env depends_on: - db db: image: postgres:16 environment: POSTGRES_USER: myuser POSTGRES_PASSWORD: mypassword POSTGRES_DB: mydb volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:
bash
docker-compose up --build

5.4 Multi-Stage Builds (Smaller Production Images)#

dockerfile
# Stage 1: build dependencies FROM python:3.12-slim AS builder WORKDIR /app COPY pyproject.toml . RUN pip install --no-cache-dir --target=/app/deps . # Stage 2: minimal final image FROM python:3.12-slim WORKDIR /app COPY --from=builder /app/deps /usr/local/lib/python3.12/site-packages COPY src/ ./src/ CMD ["uvicorn", "src.my_api.main:app", "--host", "0.0.0.0", "--port", "8000"]

6. Running in Production#

6.1 uvicorn with Multiple Workers#

bash
uvicorn my_api.main:app --host 0.0.0.0 --port 8000 --workers 4

6.2 gunicorn as a Process Manager (Common Pattern with FastAPI)#

bash
pip install gunicorn gunicorn my_api.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Gunicorn manages multiple uvicorn worker processes — restarting crashed workers, load-balancing requests across them.

6.3 Health Checks#

🐍 Python
@app.get("/health") def health_check(): return {"status": "ok"}

Most orchestration platforms (Kubernetes, load balancers) poll an endpoint like this to know whether an instance is ready to receive traffic.


7. CI/CD Basics (GitHub Actions Example)#

yaml
# .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install ".[dev]" - run: pytest - run: mypy src/

This automatically runs the test suite and type checks on every push/PR — catching issues before they merge.


8. Common Pitfalls#

INCORRECT: Copying the Entire Project Before Installing Dependencies in Docker#

dockerfile
COPY . . RUN pip install .

Every code change invalidates Docker's cache and forces a full dependency reinstall.

CORRECT: Fix — Copy Dependency Files First#

dockerfile
COPY pyproject.toml . RUN pip install --no-cache-dir . COPY . .

INCORRECT: Running as Root Inside a Container#

dockerfile
# No USER instruction — container runs as root by default, a security risk

CORRECT: Fix#

dockerfile
RUN useradd --create-home appuser USER appuser

INCORRECT: Baking Secrets Into a Docker Image#

dockerfile
ENV API_KEY=sk-abc123 # visible to anyone who inspects the image layers

CORRECT: Fix — Inject at Runtime#

bash
docker run --env-file .env my-api:latest

9. Summary & Best Practices Checklist#

  • Use pyproject.toml for new projects instead of a bare requirements.txt.
  • Use the src/ layout to avoid import-path issues during testing.
  • Never commit .env files or secrets — inject them at runtime via environment variables.
  • Order Dockerfile steps so dependency installation is cached separately from code changes.
  • Run containers as a non-root user.
  • Use multi-stage Docker builds to keep production images small.
  • Add a /health endpoint for orchestration platforms to monitor.
  • Automate tests and type checks in CI so issues are caught before deployment.
Knowledge Checkpoint

Packaging & Deployment Checkpoint

Q1.What is the modern, standardized configuration file for Python project packaging and build systems according to PEP 517/518/621?
Asetup.cfg
Bpyproject.toml
Crequirements.txt
DPipfile
Q2.What is a Python Wheel (`.whl`) file?
AA source distribution that must be compiled on every install.
BA built-package distribution format that allows fast installation without re-running setup scripts.
CA virtual environment archive.
DA Docker image wrapper.
Q3.Why should multi-stage Docker builds be used when containerizing Python applications?
ATo isolate build tools (compilers, headers) in an initial stage, resulting in a minimal, secure, lightweight final production image.
BTo run multiple Python versions concurrently in the same container.
CTo enable Docker to run without a Linux kernel.
DTo bypass Python's memory limits.
Track Your Learning

Finished studying this notebook?

Mark this guide as completed to update your course progress roadmap.