Configuration and Deployment

Back

Loading concept...

FastAPI Configuration & Deployment 🚀

The Restaurant Kitchen Analogy

Imagine you’re running a restaurant kitchen. Before customers arrive, you need to:

  • Turn on the ovens and prep ingredients (startup)
  • Know secret recipes and settings (environment variables)
  • Have a recipe book with all the rules (Pydantic settings)
  • Set up the serving window properly (server configuration)
  • Pack everything in a food truck so you can cook anywhere! (Docker)

That’s exactly what FastAPI configuration and deployment is all about!


1. Lifespan Context Manager 🎬

What Is It?

Think of the lifespan like the opening and closing routine of your restaurant.

Opening time (startup):

  • Turn on lights
  • Heat up ovens
  • Prep ingredients

Closing time (shutdown):

  • Turn off ovens
  • Clean everything
  • Lock the doors

In FastAPI, the lifespan context manager handles what happens when your app starts and stops.

Why Do We Need It?

Some things take time to set up:

  • Database connections (like warming up the oven)
  • Loading AI models (like prepping special ingredients)
  • Connecting to external services

You don’t want to do this for EVERY customer request!

Simple Example

from contextlib import asynccontextmanager
from fastapi import FastAPI

# This is like your kitchen's
# opening and closing checklist
@asynccontextmanager
async def lifespan(app: FastAPI):
    # STARTUP: Kitchen opens!
    print("Starting up...")
    # Connect to database
    # Load ML models
    yield  # App runs here
    # SHUTDOWN: Kitchen closes!
    print("Shutting down...")
    # Close database
    # Cleanup resources

app = FastAPI(lifespan=lifespan)

Real-World Example

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: Load expensive model
    app.state.model = load_ml_model()
    app.state.db = await connect_db()
    yield
    # Shutdown: Clean up
    await app.state.db.close()

app = FastAPI(lifespan=lifespan)

Key Point: Everything BEFORE yield runs at startup. Everything AFTER runs at shutdown!


2. Environment Variables 🔐

What Are They?

Environment variables are like secret notes you pass to your kitchen staff.

Imagine writing:

  • “Today’s special sauce recipe is X”
  • “The WiFi password is Y”
  • “The supplier’s phone number is Z”

You don’t put these in the recipe book (code). They change based on the day, location, or situation!

Why Use Them?

Security: Never put passwords in your code!

Flexibility: Same code works in different places:

  • Your laptop (development)
  • Test server (staging)
  • Real server (production)

How to Access Them

import os

# Reading environment variables
database_url = os.getenv("DATABASE_URL")
api_key = os.getenv("API_KEY", "default")
debug = os.getenv("DEBUG", "false")

Setting Environment Variables

In your terminal:

export DATABASE_URL="postgresql://..."
export API_KEY="super-secret-key"
export DEBUG="true"

In a .env file:

DATABASE_URL=postgresql://localhost/mydb
API_KEY=super-secret-key
DEBUG=true

Important: Never commit .env files to git! Add them to .gitignore.


3. Pydantic Settings 📋

What Is It?

Pydantic Settings is like having a smart recipe book that:

  • Knows what ingredients you need
  • Checks if ingredients are correct
  • Has default values if something is missing

Why Not Just Use os.getenv?

# The old way - lots of problems!
port = os.getenv("PORT")  # Is this a string or int?
debug = os.getenv("DEBUG")  # Is "true" == True?

Problems:

  • Everything is a string
  • No validation
  • No defaults
  • Easy to make typos

The Pydantic Way

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    api_key: str
    debug: bool = False  # Default value!
    port: int = 8000     # Auto converts!

    class Config:
        env_file = ".env"

settings = Settings()

# Now you can use:
# settings.database_url (guaranteed string)
# settings.debug (guaranteed boolean)
# settings.port (guaranteed integer)

Magic Features

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    # Different name in env vs code
    db_url: str = Field(
        alias="DATABASE_URL"
    )

    # Nested settings with prefix
    redis_host: str = "localhost"
    redis_port: int = 6379

    class Config:
        env_file = ".env"
        env_prefix = "APP_"  # APP_REDIS_HOST

Using Settings in FastAPI

from functools import lru_cache

@lru_cache  # Load settings only once!
def get_settings():
    return Settings()

@app.get("/info")
def get_info(settings = Depends(get_settings)):
    return {"debug": settings.debug}

4. Server Configuration ⚙️

What Is It?

Server configuration is like setting up your restaurant’s service window:

  • How many customers can you serve at once?
  • What’s your address?
  • When are you open?

Uvicorn: Your Server

FastAPI doesn’t run by itself. It needs a server like Uvicorn.

# Basic run
uvicorn main:app

# With options
uvicorn main:app --host 0.0.0.0 --port 8000

Key Configuration Options

uvicorn main:app \
    --host 0.0.0.0 \      # Accept all connections
    --port 8000 \          # Port number
    --workers 4 \          # Number of processes
    --reload               # Auto-reload on changes

Configuration in Code

import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        workers=4,
        reload=True,
        log_level="info"
    )

Production vs Development

Setting Development Production
reload True False
workers 1 4+
log_level debug warning
host 127.0.0.1 0.0.0.0

Gunicorn + Uvicorn (Production)

For production, use Gunicorn as a process manager:

gunicorn main:app \
    -w 4 \
    -k uvicorn.workers.UvicornWorker \
    -b 0.0.0.0:8000

5. Docker Containerization 📦

What Is Docker?

Remember our restaurant analogy? Docker is like a food truck that:

  • Has everything packed inside
  • Works the same everywhere
  • Easy to move around
  • Self-contained kitchen!

Why Docker?

Problem: “It works on my machine!”

Solution: Put your machine IN the container!

Docker packages:

  • Your code
  • Python
  • All dependencies
  • Configuration

The Dockerfile

A Dockerfile is like the blueprint for your food truck:

# Start with Python installed
FROM python:3.11-slim

# Set work directory (inside truck)
WORKDIR /app

# Copy requirements first (for caching)
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy your code
COPY . .

# Tell Docker what port to use
EXPOSE 8000

# Start command
CMD ["uvicorn", "main:app", \
     "--host", "0.0.0.0", \
     "--port", "8000"]

Building and Running

# Build the image (create the truck)
docker build -t my-fastapi-app .

# Run the container (drive the truck)
docker run -p 8000:8000 my-fastapi-app

Docker Compose

For apps with multiple services (database, cache, etc.):

# docker-compose.yml
version: "3.8"

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://db/app
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=app
      - POSTGRES_PASSWORD=secret

Run with:

docker-compose up

Best Practices

# Multi-stage build (smaller image)
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir \
    --wheel-dir /wheels -r requirements.txt

FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/*
COPY . .
CMD ["uvicorn", "main:app", \
     "--host", "0.0.0.0"]

Putting It All Together 🎯

Here’s how everything connects:

graph TD A["Your Code"] --> B["Pydantic Settings"] B --> C["Reads .env file"] B --> D["Environment Variables"] A --> E["Lifespan Manager"] E --> F["Startup Tasks"] E --> G["Shutdown Tasks"] A --> H["Uvicorn Server"] H --> I["Docker Container"] I --> J["Deployed App!"]

Complete Example

settings.py:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    api_key: str
    debug: bool = False

    class Config:
        env_file = ".env"

main.py:

from contextlib import asynccontextmanager
from fastapi import FastAPI
from settings import Settings

settings = Settings()

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup
    print(f"Debug mode: {settings.debug}")
    yield
    # Shutdown
    print("Goodbye!")

app = FastAPI(lifespan=lifespan)

@app.get("/")
def root():
    return {"status": "running"}

Dockerfile:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", \
     "--host", "0.0.0.0"]

Quick Summary 📝

Concept Purpose Like…
Lifespan Startup/Shutdown Opening/Closing routine
Env Vars Secret config Secret notes
Pydantic Type-safe settings Smart recipe book
Uvicorn Server Service window
Docker Deployment Food truck

You Did It! 🎉

You now understand how to:

  • Set up startup and shutdown routines
  • Keep secrets safe with environment variables
  • Use Pydantic for bulletproof settings
  • Configure your server properly
  • Package everything in Docker

Your FastAPI app is ready to serve the world! 🌍

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.