FastAPI + Docker 프로덕션 모범 사례 (Better Stack)

Source: https://betterstack.com/community/guides/scaling-python/fastapi-docker-best-practices/ Type: Article By: Stanley Ulili Valid as of: 2026-04-26

핵심 Takeaway

  • Dockerfile 레이어 설계: 변경 빈도가 낮은 것부터 → 의존성 → 코드 (캐싱 최대화)
  • Pydantic 설정 + .env 파일: 환경변수로 프로덕션 설정 관리, .env.example 로 예시만 git 커밋
  • 헬스체크 엔드포인트: /health 구현 후 Docker 헬스체크 설정 → 자동 재시작
  • .dockerignore: __pycache__, .git, venv/ 등 불필요 파일 제외로 빌드 시간·크기 단축
  • 로깅 전략: 구조화된 로그 → stdout (Docker가 수집) + 요청 미들웨어로 성능·에러 추적
  • DB 준비 확인: depends_on + 헬스체크로 데이터베이스 준비 완료 후 앱 시작 (race condition 방지)
  • 마이그레이션 관리: 별도 컨테이너 또는 시작 시 실행 (동시 충돌 방지)
  • 수평 확장: 단일 프로세스 컨테이너 → Nginx 역프록시로 여러 인스턴스 로드 밸런싱

상세 요약

프로덕션 고려사항 총론

프로덕션 배포는 기본 컨테이너화를 넘어 성능, 보안, 안정성, 운영성을 모두 아우른다. FastAPI + Docker의 실무 체크리스트:

  1. 성능: 레이어 최적화, 캐싱, 다중 워커
  2. 보안: 환경변수 관리, 헬스체크, 취약점 스캔
  3. 안정성: DB 준비 확인, 마이그레이션 관리, 자동 재시작
  4. 운영성: 구조화된 로깅, 요청 추적, 모니터링

1. Dockerfile 기초: 레이어 최적화

캐싱 전략의 핵심: 변경 빈도 오름차순으로 배열.

나쁜 패턴 (모든 것이 함께):

FROM python:3.9-slim
WORKDIR /app
COPY . .              # 코드 + 의존성 모두 복사
RUN pip install -r requirements.txt
CMD ["fastapi", "run", "main.py"]

문제: 소스 1줄 변경 → pip install 레이어부터 재실행 (느림)

최적 패턴 (순차적):

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .    # 변경 빈도: 낮음
RUN pip install -r requirements.txt
COPY . .                   # 변경 빈도: 높음
CMD ["fastapi", "run", "main.py"]

효과: 코드 변경 시 pip 재설치 스킵 → 빌드 시간 10배 단축 가능

2. 환경 설정 관리 (Environment Configuration)

Pydantic Settings + .env 패턴:

from pydantic_settings import BaseSettings
 
class Settings(BaseSettings):
    database_url: str
    debug: bool = False
    log_level: str = "INFO"
    
    class Config:
        env_file = ".env"
 
settings = Settings()

파일 관리:

  • .env: 실제 설정값 (개발·프로덕션 환경마다 다름) → git 무시
  • .env.example: 필요 키만 예시 → git 커밋

장점:

  • 코드 하드코딩 제거
  • 환경별 설정 분리 (DEV/PROD/TEST)
  • 보안: 민감한 정보(DB 비밀번호, API 키) 코드 외부 보관

3. 헬스체크 엔드포인트 & Docker 헬스 모니터링

헬스체크 엔드포인트 구현:

@app.get("/health")
async def health_check():
    # DB 연결 확인, 캐시 상태 등
    return {"status": "healthy"}

Docker Compose 헬스체크 설정:

services:
  app:
    build: .
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s      # 10초마다 확인
      timeout: 5s
      retries: 3        # 3회 실패 시 컨테이너 재시작
      start_period: 40s # 시작 후 40초 대기

동작:

  • 정기적으로 /health 호출
  • 실패 시 자동 재시작 → 무중단 서비스

4. .dockerignore로 빌드 최적화

# 불필요한 파일 제외
__pycache__
*.pyc
.pytest_cache
.git
.gitignore
.env           # 민감 정보
venv/
.venv/
.DS_Store
tests/         # 또는 멀티스테이지 빌드로 제거
README.md
*.md

효과:

  • 빌드 컨텍스트 크기 축소 (50MB → 10MB)
  • Docker 데몬과의 전송 시간 단축
  • 최종 이미지에 불필요 파일 미포함

5. 데이터베이스 준비 확인 (Database Readiness)

문제: Race Condition

version: '3'
services:
  db:
    image: postgres:15
  app:
    build: .
    depends_on:
      - db    # ❌ 불완전: 컨테이너 시작만 확인, 실제 준비 X

→ 앱이 시작되어도 DB가 준비 안 됨 → 연결 실패

해결책: depends_on + 헬스체크:

services:
  db:
    image: postgres:15
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 10s
      retries: 5
  app:
    build: .
    depends_on:
      db:
        condition: service_healthy   # DB 헬스체크 대기

또는 스크립트로 확인:

# prestart.sh
until pg_isready -h $DATABASE_HOST -U $DATABASE_USER; do
  sleep 2
done
exec "$@"  # 앱 시작

6. 마이그레이션 관리 (Database Migrations)

문제: 여러 앱 인스턴스가 동시에 마이그레이션 시도 → 충돌

해결책 1: 별도 초기화 컨테이너:

services:
  migrate:
    build: .
    command: "alembic upgrade head"
    depends_on:
      db:
        condition: service_healthy
  app:
    build: .
    depends_on:
      migrate:
        condition: service_completed_successfully

해결책 2: 시작 시 일회 실행:

@app.on_event("startup")
async def startup():
    # 마이그레이션 실행 (once 로직 구현)
    await run_migrations()

7. 로깅 전략 (Structured Logging)

stdout 출력 (Docker 수집):

import logging
import json
 
class JSONFormatter(logging.Formatter):
    def format(self, record):
        return json.dumps({
            "timestamp": self.formatTime(record),
            "level": record.levelname,
            "message": record.getMessage(),
            "logger": record.name
        })
 
handler = logging.StreamHandler()  # stdout
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)

요청 로깅 미들웨어:

@app.middleware("http")
async def log_requests(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    duration = time.time() - start
    logger.info(f"method={request.method} path={request.url.path} status={response.status_code} duration={duration:.3f}s")
    return response

장점:

  • Docker 로그 캡처 (stdout/stderr)
  • 중앙 로그 수집 (ELK, Splunk 등)
  • 성능 메트릭 추적 (응답 시간)
  • 에러 추적 및 디버깅

8. 수평 확장 (Horizontal Scaling)

문제: 단일 컨테이너 = 단일 프로세스 = 단일 워커 → 동시성 제한

해결책: 여러 인스턴스 + 리버스 프록시:

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app1
      - app2
      - app3
  
  app1:
    build: .
  app2:
    build: .
  app3:
    build: .

nginx.conf (로드 밸런싱):

upstream fastapi {
    server app1:8000;
    server app2:8000;
    server app3:8000;
}
 
server {
    listen 80;
    location / {
        proxy_pass http://fastapi;
    }
}

동작:

  • 클라이언트 요청 → Nginx (포트 80)
  • Nginx 라운드로빈: app1, app2, app3에 분산
  • 각 앱 인스턴스 단독 실행 (프로세스 충돌 없음)
  • 인스턴스 추가/삭제 용이

선행 개념

이 개념을 배우기 전에 필수로 알아야 할 것:

  1. Module 4-2 → fastapi-docker-official-deployment: 기본 FastAPI Dockerfile 구조
    • 왜?: 이 소스는 프로덕션 배포를 위한 고급 기법(환경변수, 보안, 성능 튜닝)을 다루므로, 기초를 먼저 알아야 함
  2. Module 5: ML 파이프라인 및 모델
    • 왜?: 실제 프로덕션 사례에서 FastAPI가 훈련된 모델을 서빙하기 때문

후속 개념 (이 개념이 선행)

이 개념을 배운 후 다음 단계:

  1. Module 6-2 → docker-compose-multi-service-orchestration: Docker Compose로 여러 서비스 통합
    • 예: “환경변수, 보안, 성능 설정을 docker-compose.yml에서 관리”
  2. Module 6-3 → structured-logging-fastapi: 프로덕션 환경에서의 로깅
    • 예: “다중 워커 환경에서 로그 수집 및 모니터링”

연결되는 위키 페이지