Are you getting into cloud-native development and looking for a real-world project to sharpen your skills? Most FastAPI tutorials show you the basics, but miss the production considerations that matter in actual deployments.
In this guide, I'll walk you through building and containerizing a FastAPI app with Docker—including the production best practices I learned while building healthcare applications and preparing for cloud certifications.
I used this exact approach in my sepsis prediction application and other healthcare systems where reliability and security aren't optional.
Why FastAPI + Docker?
FastAPI paired with Docker gives you:
- Asynchronous performance for handling multiple requests
- Automatic API documentation (crucial for team collaboration)
- Type safety with Python type hints
- Consistent environments across development, testing, and production
- Easy scaling in cloud platforms like AWS ECS or Kubernetes
What You'll Build
A production-ready FastAPI application with:
- Health checks for monitoring
- Environment-based configuration
- Proper logging
- Security headers
- Optimized Docker image
Project Structure
fastapi-docker-app/ │ ├── app/ │ ├── __init__.py │ ├── main.py │ ├── config.py │ └── health.py ├── requirements.txt ├── Dockerfile └── .env.example
1. Environment Configuration (app/config.py
)
from pydantic_settings import BaseSettings class Settings(BaseSettings): app_name: str = "FastAPI Docker App" debug: bool = False log_level: str = "INFO" class Config: env_file = ".env" settings = Settings()
2. Health Check Module (app/health.py
)
from fastapi import APIRouter import psutil import time router = APIRouter() @router.get("/health") def health_check(): return { "status": "healthy", "timestamp": time.time(), "memory_usage": f"{psutil.virtual_memory().percent}%", "cpu_usage": f"{psutil.cpu_percent()}%" } @router.get("/ready") def readiness_check(): # Add database connectivity checks here in real apps return {"status": "ready"}
3. Main Application (app/main.py
)
from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import logging import time from .config import settings from .health import router as health_router # Configure logging logging.basicConfig(level=settings.log_level) logger = logging.getLogger(__name__) app = FastAPI( title=settings.app_name, description="A production-ready FastAPI application", version="1.0.0", docs_url="/docs" if settings.debug else None, redoc_url="/redoc" if settings.debug else None ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"] if settings.debug else ["https://yourdomain.com"], allow_credentials=True, allow_methods=["GET", "POST"], allow_headers=["*"], ) # Include health check routes app.include_router(health_router, prefix="/api/v1") @app.middleware("http") async def add_security_headers(request: Request, call_next): response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "DENY" response.headers["X-XSS-Protection"] = "1; mode=block" return response @app.middleware("http") async def log_requests(request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time logger.info(f"{request.method} {request.url} - {response.status_code} - {process_time:.3f}s") return response @app.get("/") def read_root(): return { "message": "Hello from FastAPI + Docker!", "app_name": settings.app_name, "environment": "development" if settings.debug else "production" } @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): logger.error(f"Global exception: {exc}") return JSONResponse( status_code=500, content={"message": "Internal server error"} )
4. Dependencies (requirements.txt
)
fastapi uvicorn[standard] pydantic-settings psutil
5. Multi-Stage Dockerfile
# Build stage FROM python:3.11-slim as builder WORKDIR /app # Install system dependencies RUN apt-get update && apt-get install -y \ gcc \ && rm -rf /var/lib/apt/lists/* # Install Python dependencies COPY requirements.txt . RUN pip install --no-cache-dir --user -r requirements.txt # Production stage FROM python:3.11-slim # Create non-root user RUN groupadd -r appuser && useradd -r -g appuser appuser WORKDIR /app # Copy Python dependencies from builder stage COPY --from=builder /root/.local /home/appuser/.local # Copy application code COPY ./app /app # Change ownership and switch to non-root user RUN chown -R appuser:appuser /app USER appuser # Add local Python packages to PATH ENV PATH=/home/appuser/.local/bin:$PATH # Health check HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/api/v1/health || exit 1 EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
6. Environment Variables (.env.example
)
APP_NAME=FastAPI Docker App DEBUG=false LOG_LEVEL=INFO
Build & Run
# Copy environment file cp .env.example .env # Build optimized image docker build -t fastapi-app:latest . # Run with environment variables docker run -d \ --name fastapi-container \ -p 8000:8000 \ --env-file .env \ fastapi-app:latest
Test Your Application
# Basic endpoint curl http://localhost:8000/ # Health check curl http://localhost:8000/api/v1/health # API documentation (if debug=true) open http://localhost:8000/docs
Common Issues & Solutions
Issue: Container exits immediately
# Check logs docker logs fastapi-container # Common fix: Check your environment variables docker run --env-file .env fastapi-app:latest
Issue: Permission denied errors
- Make sure you're using the non-root user in Dockerfile
- Check file permissions on your host system
Issue: Health check failing
# Install curl in container if needed RUN apt-get update && apt-get install -y curl
Production Deployment Tips
AWS ECS:
- Use the health check endpoint for load balancer health checks
- Set appropriate CPU/memory limits
- Use secrets manager for sensitive environment variables
Kubernetes:
- Configure liveness and readiness probes using
/health
and/ready
- Use ConfigMaps for non-sensitive configuration
- Implement horizontal pod autoscaling
Docker Compose:
version: '3.8' services: api: build: . ports: - "8000:8000" environment: - DEBUG=false healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"] interval: 30s timeout: 10s retries: 3
What You've Learned
- Multi-stage Docker builds for smaller, secure images
- Health checks and monitoring for production reliability
- Security headers and middleware for protection
- Structured logging for debugging
- Environment-based configuration for different deployment stages
- Non-root containers for security best practices
Next Steps
- Add a database (PostgreSQL with async drivers)
- Implement authentication (JWT tokens)
- Add rate limiting for API protection
- Set up CI/CD with GitHub Actions
- Monitor with Prometheus and Grafana
Connect With Me
I'm a software engineer prepping for KCNA and AWS certifications, building healthcare and cloud-native systems using Golang, FastAPI, and Docker. Let's connect.
LinkedIn | GitHub | Substack | More Projects
Top comments (0)