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.txtbefore your code to take advantage of Docker layer caching. - Use
--host 0.0.0.0in 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.
