Deployment & Beyond
Building code on your local machine is only the beginning. Deployment means making your Python application available to the world — reliably, securely, and repeatedly. This chapter covers how to prepare Python projects for production and what to learn next.
Why This Chapter Matters
The best code that only runs on your laptop provides no value. Deployment is how your work reaches real users. Understanding the basics of packaging, environment configuration, containerization, and hosting is essential for any serious Python developer.
Development vs Production
| Aspect | Development | Production |
|---|---|---|
| Errors | Verbose, helpful | Logged privately, generic to users |
| Settings | Debug on, local DB | Debug off, production DB |
| Speed | Not critical | Optimized and monitored |
| Security | Relaxed | Hardened |
| Restarts | Manual | Automatic (process manager) |
Environment Configuration
Use environment variables (not hard-coded values) for all configuration.
.env file (never commit this!)
DATABASE_URL=postgresql://user:password@host/dbname
SECRET_KEY=super-secret-value-here
DEBUG=false
PORT=8000
Loading .env in Python
pip install python-dotenv
import os
from dotenv import load_dotenv
load_dotenv()
DATABASE_URL = os.environ.get("DATABASE_URL")
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
PORT = int(os.environ.get("PORT", 8000))
Using a Config Class
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
debug: bool = False
port: int = 8000
class Config:
env_file = ".env"
settings = Settings()
print(settings.database_url)
Packaging Your Application
requirements.txt
pip freeze > requirements.txt
Always pin exact versions in production:
fastapi==0.111.0
uvicorn==0.29.0
pydantic==2.7.0
sqlalchemy==2.0.29
pyproject.toml (Modern Approach)
[project]
name = "myapp"
version = "1.0.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.111.0",
"uvicorn>=0.29.0",
]
Docker — Containerization
Docker packages your app and all its dependencies into a container that runs identically everywhere.
Dockerfile for a FastAPI App
FROM python:3.12-slim
WORKDIR /app
# Copy and install dependencies first (layer caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose the port
EXPOSE 8000
# Run the app
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
.dockerignore
venv/
__pycache__/
.env
*.pyc
.git/
Build and Run
docker build -t myapp .
docker run -p 8000:8000 --env-file .env myapp
Docker Compose (Multiple Services)
version: "3.9"
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db/mydb
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
docker-compose up --build
Deployment Platforms
Cloud Platforms (PaaS)
| Platform | Best For |
|---|---|
| Railway | Simple FastAPI / Django apps |
| Render | Free tier web services |
| Fly.io | Docker containers, global regions |
| Heroku | Classic PaaS, Procfile-based |
| Vercel | Serverless Python functions |
Cloud Providers (IaaS)
| Provider | Service |
|---|---|
| AWS | EC2, ECS, Lambda, App Runner |
| Google Cloud | Cloud Run, GKE, App Engine |
| Azure | App Service, Container Apps |
Deploying to Railway (Simple Example)
- Create a
Procfile:
web: uvicorn main:app --host 0.0.0.0 --port $PORT
- Push to GitHub
- Connect the repo in Railway
- Set environment variables in the Railway dashboard
- Deploy
Process Management
In production, use a process manager to keep your app running and restart it on crashes.
Gunicorn + Uvicorn Workers (Production ASGI)
pip install gunicorn
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
-w 4: 4 worker processes-k uvicorn.workers.UvicornWorker: use Uvicorn's ASGI worker
Systemd (Linux Servers)
[Unit]
Description=FastAPI App
After=network.target
[Service]
User=www-data
WorkingDirectory=/app
ExecStart=/app/venv/bin/gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker
Restart=always
[Install]
WantedBy=multi-user.target
Health Checks & Logging
Always add a health check endpoint:
@app.get("/health")
def health():
return {"status": "ok", "version": "1.0.0"}
Set up structured logging:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s"
)
logger = logging.getLogger(__name__)
logger.info("Application started")
Database Migrations Before Deployment
Always run migrations before starting the server:
# Alembic migrations
alembic upgrade head
In Docker Compose, use a startup script:
#!/bin/sh
alembic upgrade head
uvicorn main:app --host 0.0.0.0 --port 8000
CI/CD — Automated Testing and Deployment
CI/CD (Continuous Integration / Continuous Deployment) runs tests automatically and deploys when tests pass.
GitHub Actions Example
name: Test and Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: pytest
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Deploy to Railway
run: railway up
What to Learn Next
After mastering these 16 chapters, the Python ecosystem offers much more:
| Topic | Tools |
|---|---|
| Web development | Django, Flask, Starlette |
| Data science | Scikit-learn, Seaborn, SciPy |
| Machine learning | PyTorch, TensorFlow, Keras |
| Data pipelines | Airflow, Prefect, dbt |
| Type checking | mypy, pyright |
| CLI tools | Typer, Click, argparse |
| Task queues | Celery, RQ, ARQ |
| GraphQL | Strawberry |
| WebSockets | FastAPI WebSockets, channels |
Common Mistakes
- committing
.envfiles to version control - running the development server (
--reload) in production - deploying without running tests first
- hard-coding ports and secrets instead of using environment variables
- not setting up health checks or logging in production
- not pinning exact dependency versions in
requirements.txt
Mini Exercises
- Add a
/healthendpoint to a FastAPI app and test it. - Create a
Dockerfilefor a simple Python script, build it, and run it. - Write a
.envfile with at least 3 settings and load them withpython-dotenv. - Create a
docker-compose.ymlthat ties your app to a PostgreSQL container. - Write a GitHub Actions workflow that runs
pyteston every push.
Review Questions
- What is the difference between development and production environments?
- Why should you never commit a
.envfile to version control? - What problem does Docker solve for deploying Python apps?
- What is the purpose of Gunicorn when running a FastAPI app in production?
- What is CI/CD and why is it valuable?
Reference Checklist
- I can configure apps using environment variables and
.envfiles - I can write a Dockerfile for a Python web application
- I can use Docker Compose to run multi-service apps locally
- I know the most common Python deployment platforms
- I understand the role of process managers like Gunicorn
- I know how to set up basic CI/CD with GitHub Actions