[FastAPI 시리즈 11편] 배포와 환경 설정 기본기

English version

기능이 갖춰졌다면 실행 환경을 프로덕션 수준으로 정리해야 합니다. 환경 변수는 운영체제가 보관하는 설정 값이라서 소스 코드에 비밀을 남기지 않고 주입할 수 있습니다. uvicorn은 FastAPI 앱을 실제 요청과 연결하는 ASGI 서버라서 배포 시 기본 실행기 역할을 합니다. uv 개발 서버는 확인용이지만, 실제 배포에서는 uvicorn, 프로세스 매니저, 환경 변수 관리가 필요합니다.

이번 글에서 새로 나오는 용어

  1. 환경 변수: 운영체제가 들고 있는 설정 값으로, 비밀번호나 DB 주소를 코드 밖에서 안전하게 주입하기 위해 사용합니다.
  2. .env: 개발용으로 환경 변수를 미리 적어 두는 파일 형식으로, pydantic-settings가 읽어 기본값 위에 덮어씁니다.
  3. pydantic-settings: 환경 변수·.env·기본값을 한 클래스에서 계층적으로 읽어 주는 라이브러리로, 글에서 Settings 클래스를 만들 때 사용합니다.
  4. 프로세스 매니저: systemd, Supervisor 같은 도구로, uvicorn 프로세스를 재시작·모니터링해 서비스가 항상 살아 있도록 유지합니다.

실습 카드

  • 예상 소요 시간: 50분
  • 사전 준비: 10편 인증 코드, .env 사용 경험
  • 실습 목표: pydantic-settings로 환경 값을 읽고 uvicorn 실행 명령을 구분한다

설정 계층 만들기

설정 계층은 환경 변수 → .env → 기본값 순으로 읽는 구조가 가장 예측 가능합니다. pydantic-settings는 이 계층을 작은 클래스로 묶어 줍니다.

운영 환경 변수.env 파일코드 기본값Settings(BaseSettings)FastAPI + uvicorn 최우선개발 시 override마지막 fallbackConfig 주입APP_* 읽기

위 흐름을 머릿속에 두면 어느 값이 실제로 적용됐는지 빠르게 추적할 수 있고, CI에서도 동일한 순서를 그대로 재현할 수 있습니다.

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str = "sqlite:///./todo.db"
    secret_key: str
    uv_reload: bool = False

    model_config = {
        "env_file": ".env",
        "env_prefix": "APP_",
    }

settings = Settings()

.env 예시:

APP_SECRET_KEY=please-change
APP_DATABASE_URL=postgresql+psycopg://user:pass@db/todo

코드에서 기본값을 제공하고 .env로 덮어쓴 뒤, 최종적으로 운영 환경 환경 변수가 가장 높은 우선순위를 가져갑니다. 이 계층 덕분에 같은 코드를 어디서든 안전하게 실행할 수 있습니다.

uvicorn 실행 전략

개발 단계에서는 자동 리로드와 상세 오류가 필요합니다.

uv run fastapi dev app/main.py

배포 단계에서는 고정된 실행 명령과 프로세스 관리가 중요합니다.

uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4

--workers는 CPU 코어 수에 맞춰 정합니다. 로드밸런서 뒤라면 --proxy-headers로 원래 클라이언트 IP를 읽습니다. 멀티 프로세스를 안정적으로 유지하려면 systemd 서비스, Supervisor, 혹은 컨테이너 오케스트레이터를 사용합니다.

정적 파일 및 미디어

정적 자산은 단순한 경우에만 FastAPI StaticFiles로 제공합니다. 대용량 이미지는 CDN이나 객체 스토리지로 위임하면 트래픽 비용을 낮출 수 있습니다. CORS 정책은 환경마다 달라지니 설정 값으로 분리해 두어야 합니다.

로깅과 모니터링


logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)

logger = logging.getLogger("todo")

로깅 포맷을 처음부터 맞춰 두면 배포 환경에서도 같은 로그를 읽을 수 있습니다. 구조화된 JSON이 필요하면 logurustructlog를 추가합니다. Prometheus나 OpenTelemetry 익스포터를 붙이면 지표 대시보드를 빠르게 만들 수 있습니다.

컨테이너 예시 Dockerfile

FROM ghcr.io/astral-sh/uv:python3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen
COPY app ./app
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

컨테이너 이미지를 만들면 CI/CD 파이프라인에서 테스트 → 이미지 빌드 → 배포 단계를 자동화할 수 있습니다. 동일 이미지가 모든 서버에 배포되므로 설정 차이가 줄어듭니다.

헬스체크와 설정 검증

/health 엔드포인트는 DB 연결과 외부 API 접속을 짧게 확인합니다. 배포 전에 Settings.model_validate를 호출해 필수 환경 변수가 빠지지 않았는지 검사합니다. 이런 검증을 CI에 넣으면 배포 실패를 사전에 막을 수 있습니다.

실습

  • 따라 하기: Settings 클래스를 만들고 .env에서 APP_SECRET_KEY를 읽어 uvicorn 실행 시 주입한다.
  • 확장하기: Dockerfile을 작성하거나 systemd 서비스 유닛을 만들어 프로덕션 실행 명령을 스크립트로 남긴다.
  • 디버깅: 환경 변수가 누락되었을 때 앱이 친절한 오류를 출력하도록 ValidationError를 확인한다.
  • 완료 기준: 로컬/프로덕션 실행 명령이 분리돼 있고, 설정 검증이 배포 전 자동화된다.

마무리

환경 변수와 uvicorn 실행 전략을 명확히 나눠 두면 배포 환경에서도 예측 가능한 동작을 유지할 수 있습니다. 지금 정리한 구성을 토대로 다음 편에서는 인증, 데이터, 설정을 묶어 작은 서비스를 완성해 보겠습니다.

💬 댓글

이 글에 대한 의견을 남겨주세요