서비스가 커질수록 환경별 설정, API 키, 데이터베이스 비밀번호 같은 민감 정보가 늘어납니다. 여기서 말하는 시크릿(secret)은 외부에 공개되면 안 되는 인증 토큰이나 비밀번호입니다. pydantic-settings는 환경 변수와 .env 파일을 손쉽게 읽어 Python 객체로 변환해 주는 도구입니다. 이번 편에서는 pydantic-settings를 중심으로 설정을 관리하고, 시크릿 저장소와 uvicorn 실행 옵션까지 살펴봅니다.
준비: 설정을 쉬운 말로 이해하기
.env파일은 "노트북 속 작은 비밀 메모장"입니다.Settings클래스는 "이 메모장을 읽어 앱 전체에 나눠 주는 우체부"입니다.- 시크릿 저장소는 "클라우드에 있는 금고"로, 팀 전체가 공유하지만 잠금열쇠가 필요합니다.
이 세 가지 역할을 떠올리면 아래 코드들이 왜 필요한지 금방 감이 옵니다.
이번 글에서 새로 나오는 용어
- 시크릿(secret): DB 비밀번호, API 키처럼 노출되면 안 되는 값으로,
.env나 시크릿 저장소에서 안전하게 주입해야 합니다. - env_prefix: 환경 변수 이름 앞에 붙일 접두사(
APP_)를 지정해 동일 서버에서 여러 앱이 값을 섞어 쓰지 않도록 하는 설정입니다. - env_nested_delimiter:
APP_FEATURE__BETA=true처럼 중첩 구조를 표현할 수 있게 해 주는 구분자로, 복잡한 설정을 깔끔하게 관리합니다. - 시크릿 저장소: AWS Secrets Manager, HashiCorp Vault 같은 외부 서비스로, 운영 환경에서 민감 정보를 안전하게 보관하고 필요할 때만 꺼내 씁니다.
실습 카드
- 예상 소요 시간: 55분 (핵심) / +30분 (선택 확장)
- 사전 준비: 16편 CORS 예제,
.env사용 경험- 실습 목표:
pydantic-settings로 환경별 값을 읽고, 필요 시 시크릿 저장소·키 전략까지 확장한다
핵심 실습: Settings + .env
pydantic-settings 클래스를 정의해 환경 변수를 안전하게 읽습니다.
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
env: str = "local"
database_url: str = "sqlite:///./todo.db"
redis_url: str | None = None
secret_key: str
uv_reload: bool = True
model_config = {
"env_file": ".env",
"env_prefix": "APP_",
"env_nested_delimiter": "__",
}
settings = Settings()
.env 파일 → 환경 변수 → 런타임 인자 순으로 로드합니다. env_nested_delimiter를 사용하면 APP_FEATURE__BETA=true처럼 중첩 설정을 표현할 수 있습니다. 이렇게 하면 설정 이름만 보고도 어떤 기능에 쓰이는지 추적하기 쉽습니다.
환경별 .env 관리
.env.local
.env.dev
.env.prod
배포 파이프라인에서는 환경별 파일을 선택해 APP_ENV=prod 같은 플래그를 주입합니다. 민감 값은 .env에 두지 않고 클라우드 시크릿 매니저에 저장한 후 런타임에 주입합니다. 버전 관리 저장소에 비밀이 남지 않아 사고를 줄일 수 있습니다.
:::terminal{title="APP_ENV 값으로 설정 확인하기", showFinalPrompt="false"}
[
{ cmd: "APP_ENV=local uv run fastapi dev app/main.py", output: "[settings] env=local reload=True database=sqlite:///./todo.db", delay: 500 },
{ cmd: "APP_ENV=prod APP_SECRET_KEY=sk_live uv run uvicorn app.main:app --workers 4", output: "[settings] env=prod reload=False workers=4", delay: 450 }
]
:::
uvicorn 실행 옵션과 설정 연계
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8000,
reload=settings.uv_reload,
workers=1 if settings.env == "local" else 4,
)
환경 값으로 리로드 여부나 워커 수를 제어하면 동일한 코드베이스를 개발/운영 환경 모두에서 재사용할 수 있습니다. 운영에서는 워커 수를 높이고, 로컬에서는 리로드를 켜 두는 식으로 동작을 맞춤화합니다.
설정 검증 테스트
def test_settings_defaults():
s = Settings(_env_file=None)
assert s.env == "local"
assert s.database_url.startswith("sqlite")
설정 자체도 테스트합니다. 기본값이 바뀌어도 즉시 감지할 수 있어 배포 사고를 줄입니다.
선택 확장: 시크릿 저장소와 키 전략
시간이 부족하면 이 섹션은 다음 세션으로 넘겨도 됩니다. 실제 운영 준비 단계에서만 다뤄도 충분합니다.
시크릿 저장소 연동 예시
AWS Secrets Manager에서 값을 가져오는 단순 예시입니다.
def load_secret(name: str) -> dict:
client = boto3.client("secretsmanager")
response = client.get_secret_value(SecretId=name)
return json.loads(response["SecretString"])
secrets = load_secret(settings.secret_name)
db_password = secrets["db_password"]
시크릿을 코드에서 직접 로드하면 부트스트랩 시간이 늘어납니다. 애플리케이션 컨테이너를 띄우기 전에 인프라 계층에서 환경 변수로 주입하도록 설계하면 스타트업 타임이 짧아집니다.
구성 계층 D2
Developer -> (.env.local)
CI/CD -> (.env.dev)
Prod -> (Secrets Manager)
Secrets Manager -> (Environment Variables)
Environment Variables -> FastAPI Settings
각 단계에서 민감 정보 노출 범위를 최소화합니다. 개발자는 로컬 파일만 접근하고, 운영 비밀은 시크릿 매니저에서만 읽도록 나눕니다.
암호화 키 관리
JWT secret_key, OAuth 클라이언트 비밀, 웹훅 서명 키는 회전(rotation) 전략을 세우고 키 식별자(KID)를 헤더에 포함해 어떤 키로 서명했는지 추적합니다. 예를 들어 Authorization: Bearer <token>의 헤더 kid를 확인해 새로운 키로 검증할지 결정합니다.
설정/시크릿 계층을 정리하면 이후 캐시나 로깅 같은 인프라 구성요소를 붙이기도 수월해집니다. 다음 편에서는 캐시와 성능 최적화 기초를 살펴봅니다.
실습
- 따라 하기:
Settings클래스를 만들고.env.local,.env.dev중 하나를 선택해 환경 값을 바꿔 본다. - 확장하기: 선택 섹션을 참고해 시크릿 매니저(또는 가짜 함수)에서 비밀을 읽어 환경 변수로 주입하는 래퍼를 만든다.
- 디버깅: 잘못된 타입이나 누락된 값이 있을 때
ValidationError메시지를 캡처하고 uvicorn 실행 옵션이 환경에 따라 바뀌는지 확인한다. - 완료 기준: 로컬/배포 설정이 분리돼 있고 시크릿·키 전략에 대한 선택 플랜을 문서로 남겼다.
마무리
환경별 설정을 코드로 모델링하면 배포 실수를 크게 줄일 수 있습니다. 시크릿을 안전하게 주입하는 습관을 지금 만들어 두면 이후 팀 협업에서도 민감 정보 노출 걱정 없이 개발할 수 있습니다.
💬 댓글
이 글에 대한 의견을 남겨주세요