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
- Environment consistency: development, testing, and production use the same image, completely saying goodbye to "environmental problems".
- Rapid deployment and delivery: Containers can be started and stopped in seconds, which is very suitable for elastic scaling.
- Resource Isolation: Each service runs in an independent container, and other services will not be affected by a dependency upgrade.
- Portability: As long as Docker can run, your application can run, whether it is a server, cloud platform or personal computer.
- 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 多阶段构建 生产部署 容器安全