FastAPI complete guide to docker-container-deployment

📂 Phase: Phase 5 - Engineering and Deployment (Practical) 🔗 Related Chapters: nginx-gunicorn-production · Pydantic Settings多环境配置

Table of contents

Why choose Docker?

In the process of software projects from development to launch, environmental inconsistency is the source of the famous "it can run on my computer" disaster. Docker packages an application and all its dependencies into a standardized container that runs consistently everywhere.

传统部署困境:
┌─────────────────────────────────────────────────────┐
│  开发:Python 3.11, PostgreSQL 13                    │
│  测试:Python 3.10, PostgreSQL 14                   │
│  生产:Python 3.11, PostgreSQL 15                   │
│                        ↓ 版本不一致、依赖冲突 ↓             │
└─────────────────────────────────────────────────────┘

Docker解决方案:
┌─────────────────────────────────────────────────────┐
│  源代码 + Dockerfile →  镜像 → 容器 → 开发/测试/生产完全一致 │
└─────────────────────────────────────────────────────┘

The core advantages brought by Docker

  1. Environment consistency: development, testing, and production use the same image, completely saying goodbye to "environmental problems".
  2. Rapid deployment and delivery: Containers can be started and stopped in seconds, which is very suitable for elastic scaling.
  3. Resource Isolation: Each service runs in an independent container, and other services will not be affected by a dependency upgrade.
  4. Portability: As long as Docker can run, your application can run, whether it is a server, cloud platform or personal computer.
  5. Rich Ecology: You can easily use Nginx, Redis, PostgreSQL and other official images in combination to quickly build a complete application environment.

This guide will take you step by step to master docker-container-deployment of FastAPI applications, from single container to multi-service orchestration, from development environment to production environment, and inject enterprise-level security and monitoring practices.

Dockerfile best practices

Dockerfile is the starting point for containerization. Writing a clear, safe, and small Dockerfile is a required course before going online.

A basic Dockerfile for production

# 使用官方轻量级 Python 镜像
FROM python:3.11-slim as base

# 设置优化项环境变量,提升运行效率和日志可读性
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

# 安装构建项目依赖所必要的系统工具
# --no-install-recommends 减少不必要的软件包
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    g++ \
    && rm -rf /var/lib/apt/lists/*

# 单独复制 requirements.txt,最大化利用 Docker 缓存
COPY requirements.txt .
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt

# 第二阶段:运行时镜像,保持最小体积
FROM python:3.11-slim

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

# 从 base 阶段复制已安装的 Python 包和可执行文件
COPY --from=base /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=base /usr/local/bin /usr/local/bin

# 为了安全,创建一个非 root 用户来运行应用
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

# 复制应用代码,并设置正确的所有权
COPY --chown=appuser:appgroup . .

USER appuser

EXPOSE 8000

# 健康检查:定期用 curl 访问服务的 /health 接口
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 生产启动命令,这里使用 uvicorn,后续会介绍 Gunicorn + uvicorn 的组合
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

💡 Tip: UseslimMirror ratioalpineMirroring is more suitable for a large number of Python projects becausealpineUsing musl-libc sometimes causes compilation or running problems with certain C extensions.

Reasonable management requirements.txt

To reduce installation time, dependency versions should be locked and optional features enabled. For example, FastAPI's standard features can be installed in one go via uvicorn's [standard] option:

fastapi>=0.109.0
uvicorn[standard]>=0.27.0
gunicorn>=21.2.0
sqlalchemy[asyncio]>=2.0.0
asyncpg>=0.29.0
redis[hiredis]>=5.0.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
pydantic-settings>=2.0.0
python-multipart>=0.0.6

Add to listuvloopandhttptools(uvicorn[standard] is included), which can significantly improve the performance of asynchronous servers.

Multi-stage build optimization

The purpose of multi-stage building is to make the final image retain only the components necessary for runtime, discarding the compilation tool chain and temporary files. If your project uses Poetry to manage dependencies, multi-stage builds can especially reduce the image size.

Below is an example of using Poetry to install dependencies and then transfer to the running image:

# ---------- 构建阶段 ----------
FROM python:3.11-slim as builder

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc g++ \
    && rm -rf /var/lib/apt/lists/*

# 安装 Poetry
RUN pip install --no-cache-dir poetry

# 仅复制依赖描述文件,利用缓存
COPY pyproject.toml poetry.lock* ./
RUN poetry config virtualenvs.create false && \
    poetry install --no-dev --no-interaction --no-ansi

# ---------- 运行阶段 ----------
FROM python:3.11-slim as runtime

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

# 保留 curl 用于健康检查
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 从 builder 复制完整的站点包
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --chown=appuser:appgroup . .
USER appuser

EXPOSE 8000

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

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

Through the above method, the final image only contains the Python runtime and installed packages, and all compilation tools are discarded, thus effectively reducing the image size.

Docker Compose orchestration

It is very simple to run a FastAPI service in a single container, but real projects often require supporting services such as database, cache, reverse proxy, etc. Docker Compose can help you start the entire environment with one command.

Local development environment

The following is a Compose file suitable for the development stage. The code is hot updated by mounting the volume, and the database and Redis have health checks.

# docker-compose.yml
version: "3.9"

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: daoman_fastapi_dev
    ports:
      - "8000:8000"
    environment:
      - ENV=development
      - DEBUG=true
      - DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/daoman_dev
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - .:/app          # 代码修改即时生效
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: postgres:16-alpine
    container_name: daoman_postgres_dev
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: daoman_dev
    ports:
      - "5432:5432"
    volumes:
      - postgres_dev_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: daoman_redis_dev
    ports:
      - "6379:6379"
    volumes:
      - redis_dev_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3

volumes:
  postgres_dev_data:
  redis_dev_data:

Production environment orchestration

The production environment requires more configuration: adding Nginx reverse proxy, fixed restart strategy, sensitive information passing.envManage and store volumes for persistence.

# docker-compose.prod.yml
version: "3.9"

services:
  nginx:
    image: nginx:alpine
    container_name: daoman_nginx_prod
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - api
    networks:
      - app-network

  api:
    build:
      context: .
      dockerfile: Dockerfile.prod
    image: daoman_fastapi:latest
    container_name: daoman_fastapi_prod
    restart: always
    expose:
      - "8000"                   # 只对内部网络暴露
    environment:
      - ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    networks:
      - app-network

  db:
    image: postgres:16-alpine
    container_name: daoman_postgres_prod
    restart: always
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_prod_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    container_name: daoman_redis_prod
    restart: always
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_prod_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres_prod_data:
  redis_prod_data:

⚠️ Safety Reminder: Never use.envThe file is submitted to version control. Sensitive information such as passwords and keys in the production environment should use Docker Secrets or CI/CD encrypted variables.

Production environment configuration

Production environments require reverse proxies to handle tasks such as request routing, SSL termination, static resource compression, and more.

Nginx reverse proxy settings

# nginx/conf.d/fastapi.conf
upstream fastapi_app {
    server api:8000;
    keepalive 32;
}

server {
    listen 80;
    server_name your-domain.com;

    access_log /var/log/nginx/fastapi.access.log;
    error_log /var/log/nginx/fastapi.error.log;

    # 增加安全头部
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    client_max_body_size 100M;

    # 启用 gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types application/javascript application/json text/css text/plain;

    location / {
        proxy_pass http://fastapi_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_buffering off;
    }

    # 健康检查端点不记录日志,减少噪音
    location /health {
        access_log off;
        proxy_pass http://fastapi_app/health;
    }
}

Example of environment variable file

# .env.production
ENV=production
DEBUG=false

DATABASE_URL=postgresql+asyncpg://user:password@host:5432/dbname
POSTGRES_DB=daoman_prod
POSTGRES_USER=daoman_user
POSTGRES_PASSWORD=your_strong_password

REDIS_URL=redis://redis:6379/0
JWT_SECRET=your_super_secret_jwt_key_here

All sensitive information must pass.envFile or more secure Secret management tool injection, written in Dockerfile or Compose file is strictly prohibited.

Security Best Practices

Container security is an ongoing process, and here are a few must-haves.

1. Fixed UID/GID for non-root users

Explicitly specifying the UID/GID (such as 1001) in the Dockerfile helps with fine-grained permission control.

FROM python:3.11-slim as base
# ... 构建阶段 ...

FROM python:3.11-slim
# ... 复制依赖 ...

RUN groupadd -r appgroup --gid 1001 && \
    useradd -r -g appgroup --uid 1001 appuser

COPY --chown=appuser:appgroup . .
USER appuser

EXPOSE 8000
# ... 健康检查与启动命令 ...

2. Restrict container permissions in Compose

Add security measures to production services, such as prohibiting new privileges, removing all kernel capabilities, and read-only root file systems.

services:
  api:
    # ... 其他配置 ...
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    read_only: true
    tmpfs:
      - /tmp
      - /var/tmp
    volumes:
      - ./logs:/app/logs:rw
      - ./uploads:/app/uploads:rw
    sysctls:
      - net.core.somaxconn=1024
    ulimits:
      nproc: 65535
      nofile:
        soft: 20000
        hard: 40000

These options can significantly reduce the impact of a compromised container.

Health Check and Monitoring

Health checks are the first line of defense to ensure service availability. In addition to the DockerfileHEALTHCHECKdirective, we also need to provide flexible/healthInterface used to report the status of database, Redis and other dependencies.

# health_check.py
from fastapi import APIRouter
from pydantic import BaseModel
from datetime import datetime
import asyncio

router = APIRouter()

class HealthStatus(BaseModel):
    status: str            # "healthy" 或 "degraded"
    timestamp: str
    services: dict

@router.get("/health", response_model=HealthStatus)
async def health_check():
    services_status = {
        "database": await check_database(),
        "redis": await check_redis(),
    }
    
    # 只有所有关键服务都是 True,才标记为 healthy
    overall_status = "healthy" if all(services_status.values()) else "degraded"
    
    return HealthStatus(
        status=overall_status,
        timestamp=datetime.now().isoformat(),
        services=services_status
    )

async def check_database():
    try:
        # 实际项目中应执行一条轻量查询,如 SELECT 1
        await asyncio.sleep(0.1)
        return True
    except Exception:
        return False

async def check_redis():
    try:
        # 实际项目中应执行 PING 命令
        await asyncio.sleep(0.05)
        return True
    except Exception:
        return False

Compatible with DockerHEALTHCHECKand nginx/healthRouting and forwarding, load balancers or orchestration tools can promptly detect unhealthy instances and restart them.

CI/CD integration

One of the biggest advantages of containerization is the seamless integration into CI/CD processes. The following takes GitHub Actions as an example to show the complete process of automated testing, building and pushing images.

# .github/workflows/docker.yml
name: Docker Build and Push

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt pytest
    - name: Run tests
      run: pytest tests/ -v

  build-and-push:
    needs: test            # 只有测试通过才会执行构建
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v4
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    - name: Login to Container Registry
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: your-registry/daoman-fastapi:latest
        cache-from: type=gha      # 利用 GitHub Actions 缓存加速构建
        cache-to: type=gha,mode=max

This workflow will automatically run a test every time the main branch code is pushed. After the test passes, a new version of the image will be built and pushed to the designated container warehouse, and then rolling update deployment can be triggered.

Frequently Asked Questions and Summary

Frequently Asked Questions (FAQ)

**Q: How to further reduce the size of the Docker image? ** A: In addition to multi-stage builds, chooseslimoralpineBasic image, clean unnecessary package cache, use.dockerignoreneglect.git, test files, etc., can help reduce the size.

**Q: Why must it be run as a non-root user? ** A: If the container is attacked, the attacker can only operate with restricted permissions and cannot easily obtain the root permissions of the host. This is a basic security protection.

**Q: How to manage sensitive information in Docker Compose? ** A: Put all keys and passwords in.envfile and be sure not to commit to version control. It can also be passed using Docker Secrets (Swarm mode) or CI/CD encrypted variables.

Summarize

Docker containerized deployment has become the best partner for FastAPI applications to go into production. Through this article, you should be able to master:

  • 🚀 Write high-quality multi-stage Dockerfile and control image size
  • 🔒 Apply non-root and Compose security options
  • 🩺 Implement application-level and container-level health checks
  • 🔄 Integrate build, test and release into CI/CD process
  • 📊 Use Docker Compose to orchestrate a complete development/production environment

Containerization is just the beginning, and you can continue to explore more advanced deployment methods such as Kubernetes and service mesh.


🔗 Recommended related tutorials

🏷️ tag cloud:FastAPI部署 Docker容器化 Dockerfile Docker Compose 多阶段构建 生产部署 容器安全