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순위 (높음) |
구현 코드 예시
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 내부에서는 다음과 같은 순서로 동작합니다.
- 파일 탐색: 설정된 경로에서 파일을 찾아 utf-8로 인코딩하여 읽습니다.
- 파싱 & 병합: 파일을 Key-Value 형태의 딕셔너리로 바꾼 후, **시스템 환경 변수(OS Environment)**와 병합합니다.
- 핵심: Pydantic은 항상 시스템 환경 변수를 파일보다 우선합니다. 운영 서버에서 Docker 등으로 주입한 환경 변수가 .env 파일보다 우선권을 가집니다.
- 타입 캐스팅: 클래스에 정의된 타입 힌트(int, bool, str)를 보고 문자열 데이터를 변환합니다.
- 인스턴스화: 모든 검증이 끝나면 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 Manager나 HashiCorp Vault 같은 서비스를 통해 환경 변수를 앱 시작 시점에 API로 받아오는 방식을 권장합니다.
요약: FastAPI 설정 관리 Best Practice
- Pydantic-Settings를 사용하여 타입 안정성을 확보하세요.
- **.env.local**을 활용해 개발자 개인 설정을 분리하고, 이는 절대 Git에 올리지 마세요.
- 우선순위를 이해하세요: 시스템 환경 변수 > .env.local > .env.dev > .env.
- 변수가 많아지면 객체 계층 구조를 만들어 가독성을 높이세요.





최근댓글