FastAPI Deploying with Docker

Docker packages your FastAPI application and all its dependencies into a container — a self-contained unit that runs the same way on any machine. No more "it works on my laptop but not the server" problems. This topic shows you how to containerize a FastAPI app from scratch.

What Docker Does

Without Docker:
  Your machine: Python 3.11, fastapi 0.100, psycopg2 2.9
  Server:       Python 3.9,  fastapi 0.88,  psycopg2 2.7
  Result:       Your app crashes on the server.

With Docker:
  Container: Python 3.11, fastapi 0.100, psycopg2 2.9  (locked)
  Runs the same on your machine, the server, or a cloud VM.

Install Docker

Download Docker Desktop from docker.com. After installation, verify it works:

docker --version
# Docker version 24.x.x

Project Structure

my-fastapi-app/
├── main.py
├── requirements.txt
└── Dockerfile

Create requirements.txt

pip freeze > requirements.txt

This captures every installed package and its version. Docker uses this file to install the same packages inside the container.

Write the Dockerfile

# Dockerfile

# 1. Start from an official Python image
FROM python:3.11-slim

# 2. Set the working directory inside the container
WORKDIR /app

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

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

# 5. Copy the rest of your code
COPY . .

# 6. Tell Docker which port the app uses
EXPOSE 8000

# 7. The command to start the app
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Why --host 0.0.0.0?
  Default host 127.0.0.1 is only accessible inside the container.
  0.0.0.0 makes the app reachable from outside the container.

Build and Run the Container

# Build the image (run from the project folder)
docker build -t my-fastapi-app .

# Run the container
docker run -p 8000:8000 my-fastapi-app
-p 8000:8000 maps:
  left  8000 → port on your machine
  right 8000 → port inside the container

Visit http://localhost:8000 to test it.

Docker Build Layers Explained

Layer 1: python:3.11-slim base image
Layer 2: WORKDIR /app
Layer 3: COPY requirements.txt
Layer 4: RUN pip install          ← cached if requirements.txt unchanged
Layer 5: COPY . .                 ← only this layer rebuilds on code changes
Layer 6: CMD [...]

Copying requirements.txt before your code means:
  Code change   → only Layer 5+6 rebuild (fast)
  Dep change    → Layer 4+5+6 rebuild (slower but rare)

Using Docker Compose for App + Database

# docker-compose.yml

version: "3.9"
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"

  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://myuser:mypassword@db/mydb
    depends_on:
      - db
Start both services:
  docker compose up

Stop them:
  docker compose down

Environment Variables in Docker

# Pass at runtime (do not hard-code secrets in Dockerfile):
docker run -p 8000:8000 \
  -e DATABASE_URL="postgresql://..." \
  -e SECRET_KEY="my-secret" \
  my-fastapi-app

# Or use a .env file:
docker run -p 8000:8000 --env-file .env my-fastapi-app

Key Points

  • A Dockerfile defines the exact environment your app runs in — OS, Python version, and packages.
  • Copy requirements.txt before your code to take advantage of Docker layer caching.
  • Use --host 0.0.0.0 in the uvicorn command so the app is reachable outside the container.
  • Use Docker Compose to run your API and database together with one command.
  • Pass secrets as environment variables at runtime — never hard-code them in the Dockerfile.

Leave a Comment

Your email address will not be published. Required fields are marked *