Python Tutorial
Configuring and Deploying FastAPI Applications
An API only becomes useful once it runs somewhere others can reach it. This lesson covers the path from laptop to production: managing configuration with environment variables and pydantic-settings, running with the production server, using multiple worker processes, packaging the app in a Docker image, and the checklist of things to get right — HTTPS, secrets, logging, health checks and database migrations.
Configuration with pydantic-settings
Settings that differ between environments — database URL, secret keys, allowed origins, debug flags — belong in environment variables, not in code (the twelve-factor approach). pydantic-settings (pip install pydantic-settings) reads them into a typed, validated BaseSettings class, optionally from a .env file during development. Missing required settings fail fast at start-up, and wrong types are rejected. Never commit .env files containing real secrets.
Running in Production
fastapi run main.py (or uvicorn main:app --host 0.0.0.0 --port 8000) starts the production server without auto-reload. Add --workers 4 to run several processes and use several CPU cores; in Kubernetes, you usually run one process per container and scale the number of containers instead. Put the app behind a reverse proxy or load balancer (Nginx, Traefik, a cloud load balancer) that terminates HTTPS, and pass --proxy-headers so FastAPI sees the real client address and scheme.
Production Checklist
- Secrets from environment variables or a secrets manager; debug off.
- HTTPS everywhere; strict CORS origins.
- Structured logging and a
/healthendpoint for load balancers and orchestrators. - Database migrations (Alembic) run as a deployment step, never create_all() on a live database.
- Pinned dependency versions, a small non-root Docker image, and automated tests in CI.
- Popular hosts: any VPS with Docker, Render, Railway, Fly.io, Google Cloud Run, AWS (ECS, App Runner, Lambda with Mangum) and Azure Container Apps.
Examples
Typed settings from environment variables
# config.py pip install pydantic-settings
import os
from functools import lru_cache
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_prefix="APP_")
app_name: str = "Webnest API"
debug: bool = False
database_url: str = "sqlite:///app.db"
secret_key: str = Field(min_length=32) # required: no default
allowed_origins: list[str] = ["http://localhost:5173"]
@lru_cache # read the environment once
def get_settings() -> Settings:
return Settings()
# Simulating the environment a server would provide:
os.environ["APP_SECRET_KEY"] = "x" * 40
os.environ["APP_DEBUG"] = "true"
os.environ["APP_ALLOWED_ORIGINS"] = '["https://webnest.example"]'
settings = get_settings()
print(settings.app_name, settings.debug, settings.database_url)
print(settings.allowed_origins, len(settings.secret_key))
os.environ["APP_SECRET_KEY"] = "too-short"
try:
Settings()
except Exception as e:
print(type(e).__name__, "-", e.errors()[0]["msg"])
Webnest API True sqlite:///app.db
['https://webnest.example'] 40
ValidationError - String should have at least 32 characters
Production commands
pip freeze > requirements.txt # or use uv / Poetry lock files
fastapi run app/main.py --port 8000 # production server, no reload
fastapi run app/main.py --workers 4 # four worker processes
# Equivalent with Uvicorn directly, behind a reverse proxy:
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 --proxy-headers
alembic upgrade head # apply database migrations before starting
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started parent process [1]
INFO: Started server process [8]
INFO: Started server process [9]
INFO: Started server process [10]
INFO: Started server process [11]
INFO: Application startup complete.
A Dockerfile for a FastAPI application
# Dockerfile
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["fastapi", "run", "app/main.py", "--port", "8000", "--proxy-headers"]
# Build and run:
# docker build -t webnest-api .
# docker run -p 8000:8000 --env-file .env webnest-api
Successfully tagged webnest-api:latest
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Common Mistakes
- Running fastapi dev / --reload in production.
- Committing .env files or hard-coding secrets in the Docker image.
- Running containers as root and shipping huge images with build tools included.
- Creating tables with create_all() on a production database instead of running migrations.
- Forgetting --proxy-headers behind a proxy, so generated URLs use http instead of https.
Key Points to Remember
- Keep configuration in environment variables; pydantic-settings validates it.
- fastapi run (or uvicorn) serves production traffic; --workers uses more CPU cores.
- Terminate HTTPS at a reverse proxy or load balancer.
- Docker packages the app and its dependencies into a reproducible image.
- Add health checks, logging, migrations and CI tests to every deployment.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local project environment for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.