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

AspectDevelopmentProduction
ErrorsVerbose, helpfulLogged privately, generic to users
SettingsDebug on, local DBDebug off, production DB
SpeedNot criticalOptimized and monitored
SecurityRelaxedHardened
RestartsManualAutomatic (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)

PlatformBest For
RailwaySimple FastAPI / Django apps
RenderFree tier web services
Fly.ioDocker containers, global regions
HerokuClassic PaaS, Procfile-based
VercelServerless Python functions

Cloud Providers (IaaS)

ProviderService
AWSEC2, ECS, Lambda, App Runner
Google CloudCloud Run, GKE, App Engine
AzureApp Service, Container Apps

Deploying to Railway (Simple Example)

  1. Create a Procfile:
web: uvicorn main:app --host 0.0.0.0 --port $PORT
  1. Push to GitHub
  2. Connect the repo in Railway
  3. Set environment variables in the Railway dashboard
  4. 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:

TopicTools
Web developmentDjango, Flask, Starlette
Data scienceScikit-learn, Seaborn, SciPy
Machine learningPyTorch, TensorFlow, Keras
Data pipelinesAirflow, Prefect, dbt
Type checkingmypy, pyright
CLI toolsTyper, Click, argparse
Task queuesCelery, RQ, ARQ
GraphQLStrawberry
WebSocketsFastAPI WebSockets, channels

Common Mistakes

  • committing .env files 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

  1. Add a /health endpoint to a FastAPI app and test it.
  2. Create a Dockerfile for a simple Python script, build it, and run it.
  3. Write a .env file with at least 3 settings and load them with python-dotenv.
  4. Create a docker-compose.yml that ties your app to a PostgreSQL container.
  5. Write a GitHub Actions workflow that runs pytest on every push.

Review Questions

  1. What is the difference between development and production environments?
  2. Why should you never commit a .env file to version control?
  3. What problem does Docker solve for deploying Python apps?
  4. What is the purpose of Gunicorn when running a FastAPI app in production?
  5. What is CI/CD and why is it valuable?

Reference Checklist

  • I can configure apps using environment variables and .env files
  • 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

Back to Python Course Overview