FastAPI로 개발한 API를 로컬에서는 잘 돌아가는데, 프로덕션 환경에 배포하려니 뭘 먼저 해야 할지 막막한 경험, 있으신가요? uvicorn main:app --reload 명령어 하나로 개발할 때는 문제없지만, 실서비스 환경에서는 동시성 처리, 보안, 모니터링, 장애 복구 등 고려할 게 한두 가지가 아닙니다.

이 글에서는 FastAPI 애플리케이션을 프로덕션에 배포할 때 반드시 알아야 할 핵심 패턴을 단계별로 정리합니다. Docker 컨테이너화부터 Gunicorn 워커 설정, 보안 미들웨어, 헬스체크, 그리고 모니터링까지, 실무에서 바로 적용할 수 있는 코드와 설정을 중심으로 설명하겠습니다.
1. 프로덕션 프로젝트 구조 잡기
프로덕션 환경에서는 기능별로 모듈을 분리하는 것이 필수입니다. 라우터(Router), 서비스(Service), 모델(Model)을 명확히 나누면 유지보수와 확장이 훨씬 쉬워집니다.
my-api/
├── app/
│ ├── main.py # FastAPI 앱 진입점
│ ├── config/
│ │ └── settings.py # Pydantic Settings (환경변수 관리)
│ ├── routers/
│ │ ├── auth.py # 인증 엔드포인트
│ │ └── items.py # 비즈니스 엔드포인트
│ ├── services/
│ │ └── item_service.py # 비즈니스 로직
│ ├── models/
│ │ └── schemas.py # Pydantic 모델
│ └── db/
│ └── database.py # DB 커넥션 관리
├── Dockerfile
├── docker-compose.yaml
├── gunicorn.conf.py # Gunicorn 설정
├── pyproject.toml
└── .env.example
이 구조의 핵심은 라우터는 HTTP 처리만, 서비스는 비즈니스 로직만 담당한다는 점입니다. 이렇게 분리하면 새로운 기능을 추가할 때 기존 코드를 건드리지 않아도 됩니다.
2. Pydantic Settings로 환경변수 관리
하드코딩된 설정값은 프로덕션에서 보안 문제를 일으킵니다. Pydantic Settings를 사용하면 타입 안전하게 환경변수를 관리할 수 있습니다.
# app/config/settings.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
class Settings(BaseSettings):
PROJECT_NAME: str = "MyAPI"
API_V1_STR: str = "/api/v1"
# 보안
SECRET_KEY: str
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
# 데이터베이스
DATABASE_URL: str # postgresql+asyncpg://...
# Redis (캐시, Celery)
REDIS_URL: str = "redis://localhost:6379/0"
# CORS
BACKEND_CORS_ORIGINS: list[str] = ["http://localhost:3000"]
model_config = SettingsConfigDict(
env_file=".env",
case_sensitive=True,
extra="ignore" # 알 수 없는 환경변수는 무시
)
settings = Settings() # 앱 시작 시 필수 변수 누락이면 즉시 에러
Settings()를 호출하는 순간, 필수 필드(SECRET_KEY, DATABASE_URL 등)가 누락되면 앱이 시작되지 않습니다. 이 "Fail Fast" 방식이야말로 프로덕션에서 설정 오류를 조기에 발견하는 최선의 방법입니다.
3. ASGI 서버: Gunicorn + Uvicorn 조합
단일 Uvicorn 프로세스는 개발용입니다. 프로덕션에서는 Gunicorn이 여러 Uvicorn 워커 프로세스를 관리하는 구조를 사용합니다.
# gunicorn.conf.py
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() # CPU 코어 수만큼
worker_class = "uvicorn.workers.UvicornWorker"
preload_app = True # 포크 전 코드 로드 → 메모리 절약
# 워커 재시작 (메모리 누수 방지)
max_requests = 1000
max_requests_jitter = 50 # 1000~1050 요청 후 재시작
# 타임아웃
graceful_timeout = 30
timeout = 120
# 동시 연결
worker_connections = 1000
# 로그에 응답 시간 포함
accesslog = "-"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(D)s'
핵심 포인트:
workers = cpu_count(): 동기 워커의(2 × CPU) + 1공식과 달리, 비동기 Uvicorn 워커는 코어 수와 동일하게 설정합니다. 하나의 워커가 이미 수천 개의 동시 요청을 처리할 수 있기 때문입니다.max_requests: 장시간 실행 시 메모리 누수가 누적되는 것을 방지합니다. jitter를 주면 모든 워커가 동시에 재시작되는 현상을 막을 수 있습니다.preload_app = True: Copy-on-Write 최적화로 메모리 사용량을 크게 줄입니다.
4. 비동기 데이터베이스 세션 관리
FastAPI는 비동기 프레임워크이므로, 데이터베이스 세션도 비동기로 관리해야 성능이最大化됩니다.
# app/db/database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from app.config.settings import settings
async_engine = create_async_engine(
settings.DATABASE_URL,
echo=False,
pool_size=10, # 기본 연결 풀 크기
max_overflow=20, # 초과 연결 수
pool_pre_ping=True, # 연결 유효성 사전 확인
)
AsyncSessionLocal = async_sessionmaker(
async_engine,
class_=AsyncSession,
expire_on_commit=False,
)
# 의존성 주입
async def get_db():
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()

pool_pre_ping=True는 MySQL의 wait_timeout이나 네트워크 단절로 끊어진 연결을 자동으로 감지하고 복구합니다. pool_size=10, max_overflow=20이면 최대 30개의 동시 DB 연결을 사용할 수 있습니다. 이 값은 앱의 동시 요청 수와 DB 서버의 max_connections 설정에 맞춰 조정해야 합니다.
5. 보안 미들웨어 설정
프로덕션 API에는 CORS, Rate Limiting, 보안 헤더가 필수입니다.
# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from app.config.settings import settings
limiter = Limiter(key_func=get_remote_address)
app = FastAPI(title=settings.PROJECT_NAME)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.BACKEND_CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Rate Limiting
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# 보안 헤더 미들웨어
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
return response
slowapi 라이브러리는 Redis를 백엔드로 사용하는 Rate Limiting을 제공합니다. 엔드포인트별로 @limiter.limit("10/minute") 데코레이터를 추가하면, 분당 10회 요청으로 제한할 수 있습니다.
6. 헬스체크 엔드포인트
로드 밸런서와 오케스트레이션 플랫폼(Kubernetes, ECS 등)은 앱이 정상인지 확인하기 위해 헬스체크를 사용합니다. Liveness(프로세스 살아있음)와 Readiness(의존성 준비됨)를 분리하는 것이 좋습니다.
# app/routers/health.py
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from app.db.database import get_db
import redis
router = APIRouter()
@router.get("/health/live")
async def liveness():
"""프로세스가 살아있는지 확인"""
return {"status": "alive"}
@router.get("/health/ready")
async def readiness(db: AsyncSession = Depends(get_db)):
"""DB, Redis 등 의존성이 준비되었는지 확인"""
checks = {}
# DB 연결 확인
try:
await db.execute(text("SELECT 1"))
checks["database"] = "ok"
except Exception as e:
checks["database"] = f"error: {str(e)}"
# Redis 연결 확인
try:
r = redis.from_url(settings.REDIS_URL)
r.ping()
checks["redis"] = "ok"
except Exception as e:
checks["redis"] = f"error: {str(e)}"
all_ok = all(v == "ok" for v in checks.values())
return {
"status": "ready" if all_ok else "degraded",
"checks": checks
}
Readiness 체크가 실패하면 로드 밸런서가 해당 인스턴스로 트래픽을 보내지 않습니다. 이는 배포 중 무중단 업데이트(Zero-downtime deploy)를 구현할 때 핵심적인 역할을 합니다.
7. Docker 컨테이너화
마지막으로 전체를 Docker 이미지로 패키징합니다.
# Dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
FROM python:3.12-slim
WORKDIR /app
RUN useradd -m appuser
# 빌더에서 의존성만 복사 (이미지 크기 최소화)
COPY --from=builder /app/.venv /app/.venv
COPY . .
ENV PATH="/app/.venv/bin:$PATH"
USER appuser
EXPOSE 8000
CMD ["gunicorn", "app.main:app", "-c", "gunicorn.conf.py"]
다단계 빌드(Multi-stage build)를 사용하면 최종 이미지에 빌드 도구가 포함되지 않아 이미지 크기를 70% 이상 줄일 수 있습니다. uv sync --frozen은 uv.lock 파일에 고정된 버전만 설치하므로, 배포마다 의존성이 변경되는 사고를 방지합니다.
# docker-compose.yaml
version: "3.9"
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/mydb
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY=${SECRET_KEY}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health/live"]
interval: 30s
timeout: 5s
retries: 3
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 10s
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
volumes:
pgdata:
요약: 프로덕션 배포 체크리스트
FastAPI를 프로덕션에 배포하기 전 아래 항목들을 모두 확인하세요.
- Gunicorn + Uvicorn — 단일 Uvicorn 대신 Gunicorn으로 멀티 워커 관리
- Pydantic Settings — 환경변수 타입 안전 관리, 필수값 누락 시 즉시 실패
- 비동기 DB 세션 —
pool_pre_ping으로 끊어진 연결 자동 복구 - CORS + Rate Limiting —
slowapi로 엔드포인트별 요청 제한 - 보안 헤더 — X-Content-Type-Options, X-Frame-Options 등 필수 헤더 추가
- 헬스체크 — Liveness/Readiness 분리, DB·Redis 의존성 확인
- Docker 다단계 빌드 —
uv sync --frozen으로 재현 가능한 이미지 - 워커 재시작 정책 —
max_requests로 메모리 누수 방지
이 패턴들을 하나씩 적용해 나가면, 개발 중의 빠른 프로토타이핑 장점을 유지하면서도 프로덕션에서 요구되는 안정성과 성능을 동시에 확보할 수 있습니다. 처음부터 모든 걸 다 적용할 필요는 없습니다. 프로젝트 규모와 트래픽에 맞춰 점진적으로 도입하는 것도 좋은 전략입니다.
'개발 팁' 카테고리의 다른 글
| GitHub 워크플로우 최적화 완벽 가이드 — 개발 생산성을 2배로 높이는 7가지 전략 (0) | 2026.04.19 |
|---|---|
| Git 브랜치 전략 비교 분석 — GitFlow vs GitHub Flow vs Trunk-Based Development (0) | 2026.04.18 |
| LLM 입문 가이드 — 개발자가 알아야 할 핵심 정리 (0) | 2026.04.17 |
| K-드라마 해외 반응 분석 — 오징어 게임 1억 시청, 디지털 피드백 루프가 만드는 글로벌 히트 (1) | 2026.04.17 |
| 2026년 최고의 오픈월드 게임 순위 — 지금 바로 시작해야 할 TOP 10 (0) | 2026.04.16 |