FastAPI 애플리케이션을 개발할 때 데이터베이스 주소, API 키, 보안 토큰과 같은 민감한 정보는 소스 코드에 직접 노출해서는 안 됩니다. 이를 위해 .env 파일을 활용하는데, 단순히 값을 읽어오는 것을 넘어 타입 검증과 계층적 관리를 적용하는 것이 현대적인 표준입니다.

1. 왜 Pydantic-Settings인가?

과거에는 python-dotenv 라이브러리로 환경 변수를 읽어 os.environ을 통해 접근했습니다. 하지만 이 방식은 값이 문자열로만 읽히고, 필수 값이 누락되어도 실행 시점에 알기 어렵다는 단점이 있습니다.

FastAPI의 표준인 **pydantic-settings**를 사용하면 다음과 같은 이점이 있습니다.

  • 자동 타입 변환: "5432"를 정수 5432로, "true"를 불리언 True로 자동 캐스팅합니다.
  • 유효성 검증: 필수 변수가 없거나 타입이 틀리면 서버 시작 단계에서 에러를 발생시켜 서비스 장애를 방지합니다 (Fail-fast).
  • 자동 문서화: 설정값이 구조화되어 관리가 용이합니다.

2. 실무형 계층적 환경 변수 관리 전략

실무에서는 **공통 설정, 환경별 설정(Dev/Prd), 그리고 개인 설정(.local)**을 나누어 관리합니다.

환경 변수 파일의 계층 구조

파일명 용도 Git 관리 여부 우선순위
.env 모든 환경 공통 설정 (앱 이름 등) 포함 (O) 3순위 (낮음)
.env.dev / .env.prd 개발/운영 서버 전용 설정 (DB 주소 등) 포함 (O) 2순위 (중간)
.env.local 개발자 개인 설정 (로컬 DB 비번 등) 제외 (X) 1순위 (높음)

구현 코드 예시

Python
 
from pydantic_settings import BaseSettings, SettingsConfigDict
import os

# 현재 실행 환경 (dev, prd 등)
env_state = os.getenv("ENV_STATE", "dev")

class Settings(BaseSettings):
    # 변수 정의(.env 항목과 동일하게 적어야함)
    DB_URL: str
    DEBUG: bool = False
    
    # 계층적 로드 설정
    model_config = SettingsConfigDict(
        env_file=(".env", f".env.{env_state}", ".env.local"),
        env_file_encoding="utf-8",
        extra="ignore" # 정의되지 않은 변수는 무시
        # extra="allow" # .env에 미리 정의된 항목 이외의 변수도 허용
    )
    
    # V2부터의 model_config가 일반적인 방식, 이전 방식은 아래와 같음
    # class Config:
    #    env_file = CommonSettings.Config.env_file + [SERVICE_ENV_FILE]
    #    if env == "local" and LOCAL_SERVICE_ENV_FILE.exists():
    #        env_file.append(LOCAL_SERVICE_ENV_FILE)
    #    env_file_encoding = "utf-8"
    #    extra = "allow"  # 추가 필드 허용 설정

settings = Settings()
  • 작동 원리: 리스트의 뒤에 있을수록 우선순위가 높습니다. .env.local에 값이 있다면 앞의 파일들에 적힌 값을 덮어씁니다.

3. 내부 동작 원리: 어떻게 파일이 객체가 되는가?

우리가 class Config나 model_config를 설정할 때, Pydantic 내부에서는 다음과 같은 순서로 동작합니다.

  1. 파일 탐색: 설정된 경로에서 파일을 찾아 utf-8로 인코딩하여 읽습니다.
  2. 파싱 & 병합: 파일을 Key-Value 형태의 딕셔너리로 바꾼 후, **시스템 환경 변수(OS Environment)**와 병합합니다.
  3. 핵심: Pydantic은 항상 시스템 환경 변수를 파일보다 우선합니다. 운영 서버에서 Docker 등으로 주입한 환경 변수가 .env 파일보다 우선권을 가집니다.
  4. 타입 캐스팅: 클래스에 정의된 타입 힌트(int, bool, str)를 보고 문자열 데이터를 변환합니다.
  5. 인스턴스화: 모든 검증이 끝나면 settings.DB_URL과 같이 접근 가능한 객체로 메모리에 적재됩니다.

4. 대규모 프로젝트를 위한 관리 팁

환경 변수가 50개, 100개가 넘어간다면 어떻게 해야 할까요?

① 그룹화(Nested Models)

모든 변수를 한곳에 넣지 말고 의미 단위로 쪼개세요.

class DatabaseSettings(BaseModel):
    host: str
    port: int

class Settings(BaseSettings):
    db: DatabaseSettings  # DB_HOST, DB_PORT를 자동으로 매핑
    api_key: str

② .env.example 운영

실제 .env는 보안상 Git에 올리지 않지만, 어떤 변수가 필요한지 알려주는 **가이드 파일(.env.example)**은 반드시 공유해야 합니다. 동료 개발자는 이 파일을 복사해 자신의 .env.local을 만듭니다.

③ 외부 보안 저장소 활용

운영 환경(Production)에서는 아예 파일을 쓰지 않고 AWS Secrets ManagerHashiCorp Vault 같은 서비스를 통해 환경 변수를 앱 시작 시점에 API로 받아오는 방식을 권장합니다.


요약: FastAPI 설정 관리 Best Practice

  1. Pydantic-Settings를 사용하여 타입 안정성을 확보하세요.
  2. **.env.local**을 활용해 개발자 개인 설정을 분리하고, 이는 절대 Git에 올리지 마세요.
  3. 우선순위를 이해하세요: 시스템 환경 변수 > .env.local > .env.dev > .env.
  4. 변수가 많아지면 객체 계층 구조를 만들어 가독성을 높이세요.
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기