Skip to main content

FastAPI Docker Deployment

Introduction

Docker has revolutionized how we deploy applications by providing a consistent environment across development, testing, and production. When combined with FastAPI, Docker offers a powerful solution for creating scalable, maintainable microservices and web applications.

In this tutorial, we'll learn how to containerize a FastAPI application using Docker. This approach ensures that your application runs in the same environment regardless of where it's deployed, eliminating the infamous "but it works on my machine" problem.

Prerequisites

Before we start, you should have:

  • Basic knowledge of FastAPI
  • Docker installed on your system
  • A simple FastAPI application to containerize

Understanding Docker Basics

Docker allows you to package your application with all its dependencies into a standardized unit called a container. These containers are isolated from each other and bundle their own software, libraries, and configuration files.

Key Docker concepts:

  • Docker Image: A read-only template with instructions for creating a Docker container
  • Dockerfile: A text document with commands to assemble a Docker image
  • Container: A runnable instance of an image
  • Docker Compose: A tool for defining and running multi-container applications

Creating a Simple FastAPI Application

Let's start by creating a simple FastAPI application that we'll later dockerize.

python
# main.py
from fastapi import FastAPI

app = FastAPI(title="DockerizedAPI")

@app.get("/")
async def read_root():
return {"message": "Hello from FastAPI in Docker"}

@app.get("/items/{item_id}")
async def read_item(item_id: int, query_param: str = None):
return {"item_id": item_id, "query_param": query_param}

This is a basic FastAPI application with two endpoints. You would typically run this using Uvicorn:

bash
uvicorn main:app --host 0.0.0.0 --port 8000

Creating a Dockerfile

Now let's create a Dockerfile to containerize our FastAPI application:

dockerfile
# Use official Python runtime as a parent image
FROM python:3.9-slim

# Set the working directory
WORKDIR /app

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

# Copy the current directory contents into the container
COPY . .

# Make port 8000 available to the world outside this container
EXPOSE 8000

# Define environment variable
ENV MODULE_NAME="main"
ENV VARIABLE_NAME="app"
ENV PORT=8000

# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Let's also create a requirements.txt file:

fastapi>=0.68.0
uvicorn>=0.15.0

Understanding the Dockerfile

Let's break down our Dockerfile:

  1. FROM python:3.9-slim: This is our base image. We're using Python 3.9 with the slim variant to keep the image size smaller.

  2. WORKDIR /app: Sets the working directory inside the container.

  3. COPY requirements.txt .: Copies the requirements file into the container.

  4. RUN pip install --no-cache-dir -r requirements.txt: Installs the Python dependencies.

  5. COPY . .: Copies the application code into the container.

  6. EXPOSE 8000: Documents that the container listens on port 8000.

  7. ENV MODULE_NAME...: Sets environment variables.

  8. CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]: The command that runs when the container starts.

Building the Docker Image

Now that we have our Dockerfile, let's build our Docker image:

bash
docker build -t fastapi-app .

This command builds an image and tags it as fastapi-app. The . tells Docker to look for the Dockerfile in the current directory.

Running the Docker Container

Once the image is built, we can run it as a container:

bash
docker run -d -p 8000:8000 --name my-fastapi-container fastapi-app

Let's break this down:

  • -d: Run the container in detached mode (in the background)
  • -p 8000:8000: Map port 8000 of the host to port 8000 in the container
  • --name my-fastapi-container: Name our container
  • fastapi-app: The image to use

Now your FastAPI application should be running inside a Docker container! You can access it at http://localhost:8000.

Using Docker Compose for More Complex Applications

For applications with multiple services (like FastAPI + Database), Docker Compose is ideal. Let's create a docker-compose.yml file:

yaml
version: '3'

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

db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=app
ports:
- "5432:5432"

volumes:
postgres_data:

With this setup, you can start both services with:

bash
docker-compose up -d

Advanced Dockerization Tips

1. Multi-stage Builds

For more efficient Docker images, you can use multi-stage builds:

dockerfile
# Build stage
FROM python:3.9-slim AS builder

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Runtime stage
FROM python:3.9-slim

WORKDIR /app

# Copy only the installed packages from the builder
COPY --from=builder /usr/local/lib/python3.9/site-packages/ /usr/local/lib/python3.9/site-packages/

COPY . .

EXPOSE 8000

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

2. Using Environment Variables for Configuration

Configure your app to read from environment variables:

python
import os
from fastapi import FastAPI

app = FastAPI(title=os.getenv("APP_TITLE", "DockerizedAPI"))

@app.get("/")
async def read_root():
environment = os.getenv("ENVIRONMENT", "development")
return {"message": f"Hello from {environment} environment"}

Then set these in your Dockerfile or docker-compose.yml:

dockerfile
ENV ENVIRONMENT="production"
ENV APP_TITLE="Production API"

3. Health Checks

Adding health checks to your Dockerfile ensures Docker can monitor your application's health:

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

This assumes you have a /health endpoint in your FastAPI app:

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

Deploying Your Dockerized FastAPI App

To a Cloud Provider

Most cloud providers support Docker containers:

  • AWS: Use Elastic Container Service (ECS) or Elastic Kubernetes Service (EKS)
  • Google Cloud: Use Google Kubernetes Engine (GKE) or Cloud Run
  • Azure: Use Azure Kubernetes Service (AKS) or Container Instances

Example for deploying to AWS ECS:

  1. Push your image to Amazon ECR:
bash
# Create a repository
aws ecr create-repository --repository-name fastapi-app

# Login to ECR
aws ecr get-login-password | docker login --username AWS --password-stdin <your-aws-account-id>.dkr.ecr.<region>.amazonaws.com

# Tag the image
docker tag fastapi-app:latest <your-aws-account-id>.dkr.ecr.<region>.amazonaws.com/fastapi-app:latest

# Push the image
docker push <your-aws-account-id>.dkr.ecr.<region>.amazonaws.com/fastapi-app:latest
  1. Create an ECS task definition and service to run your container.

To Kubernetes

  1. Create a Kubernetes deployment file deployment.yaml:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-app
spec:
replicas: 3
selector:
matchLabels:
app: fastapi-app
template:
metadata:
labels:
app: fastapi-app
spec:
containers:
- name: fastapi-app
image: fastapi-app:latest
ports:
- containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: fastapi-app
spec:
selector:
app: fastapi-app
ports:
- port: 80
targetPort: 8000
type: LoadBalancer
  1. Apply the deployment:
bash
kubectl apply -f deployment.yaml

Common Issues and Troubleshooting

1. Container Fails to Start

Check the logs of your container:

bash
docker logs my-fastapi-container

2. Can't Connect to the API

Ensure ports are correctly mapped and the container is running:

bash
docker ps

3. Database Connection Issues

When using Docker Compose, ensure service names match connection strings. For example, if your database service is named db, your connection string should use db as the host, not localhost.

Summary

In this tutorial, you've learned how to:

  1. Create a Dockerfile for a FastAPI application
  2. Build and run Docker containers
  3. Use Docker Compose for multi-service applications
  4. Implement advanced Docker techniques
  5. Deploy your containerized application

Containerizing your FastAPI applications with Docker provides consistency across environments, simplifies deployment, and makes scaling easier. This approach is particularly valuable in microservices architectures and when working in teams where environment consistency is crucial.

Additional Resources

Exercises

  1. Modify the Dockerfile to use a non-root user for better security.
  2. Add a Redis service to your docker-compose.yml and integrate it with your FastAPI app.
  3. Create a production-ready Dockerfile that includes proper logging configuration.
  4. Set up a CI/CD pipeline to automatically build and deploy your Docker container.
  5. Implement environment-specific configuration using Docker environment variables.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)