FastAPI in Docker: A Guide to Building, Running, and Scaling Your API

In today’s microservices architecture, containerization has become crucial to application deployment. This guide will walk you through containerizing a FastAPI application using Docker, from basic setup to production-ready deployment.

Prerequisites

Before we begin, ensure you have the following installed:

  • Python 3.8+
  • Docker
  • Docker Compose (optional, but recommended)
  • FastAPI and Uvicorn

Project Setup

Let’s start with a basic FastAPI project structure:

my_fastapi_app/
├── app/
│ ├── init.py
│ ├── main.py
│ └── api/
│ └── init.py
├── Dockerfile
├── docker-compose.yml
└── requirements.txt

Create a simple FastAPI application in app/main.py:

from fastapi import FastAPI

app = FastAPI(title="My FastAPI App")

@app.get("/")
async def root():
    return {"message": "Hello, Docker!"}

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

Dockerizing Your FastAPI Application

Create a Dockerfile:

# Use official Python runtime as the base image
FROM python:3.9-slim

# Set working directory in the container
WORKDIR /app

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Install system dependencies
RUN apt-get update \
    && apt-get install -y --no-install-recommends gcc \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

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

# Copy the project files into the container
COPY ./app ./app

# Command to run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Your requirements.txt should include:

fastapi>=0.68.0
uvicorn>=0.15.0
pydantic>=1.8.0

Development vs. Production Configurations

Create a docker-compose.yml for development:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - ./app:/app/app
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
    environment:
      - ENVIRONMENT=development
      - DEBUG=1

For production, create a docker-compose.prod.yml:

version: '3.8'

services:
  web:
    build: 
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
    environment:
      - ENVIRONMENT=production
      - DEBUG=0
    restart: unless-stopped

Best Practices and Optimization

1. Multi-stage Builds

For production, use multi-stage builds to reduce image size:

# Build stage
FROM python:3.9-slim as builder

WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt

# Final stage
FROM python:3.9-slim

WORKDIR /app
COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .
RUN pip install --no-cache /wheels/*

COPY ./app ./app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

2. Security Best Practices

  • Run as a non-root user
  • Use specific package versions
  • Implement health checks

Add to your Dockerfile:

# Create non-root user
RUN adduser --system --group app
USER app

# Add health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

Scaling with Docker Compose

For horizontal scaling, update your docker-compose.prod.yml:

version: '3.8'

services:
  web:
    build: .
    deploy:
      replicas: 4
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web

Deployment Considerations

  1. Environment Variables
    • Use .env files for development
    • Use secure secrets management in production
    • Never commit sensitive data to version control
  2. Logging Configure logging in your FastAPI application:
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

Monitoring

  • Implement Prometheus metrics
  • Use container monitoring solutions
  • Set up alerting for critical issues

Conclusion

Containerizing your FastAPI application with Docker provides numerous benefits:

  • Consistent development environments
  • Easy scaling and deployment
  • Better resource utilization
  • Simplified dependency management

Remember to:

  • Always use production-grade ASGI servers like Uvicorn or Gunicorn
  • Implement proper logging and monitoring
  • Follow security best practices
  • Optimize your Docker images for production

By following this guide, you’ll have a solid foundation for deploying FastAPI applications in containers, ready for production use.

Atiqur Rahman

I am MD. Atiqur Rahman graduated from BUET and is an AWS-certified solutions architect. I have successfully achieved 6 certifications from AWS including Cloud Practitioner, Solutions Architect, SysOps Administrator, and Developer Associate. I have more than 8 years of working experience as a DevOps engineer designing complex SAAS applications.

Leave a Reply