API 키나 데이터베이스 비밀번호를 코드에 직접 쓰다가 GitHub에 올려서 당황한 경험, 있으신가요? 저도 그랬습니다. 환경변수를 쓰면 이런 실수를 막을 수 있다는 걸 나중에 알게 됐죠. 실제로 보안 사고의 74%가 하드코딩된 인증정보 때문이라는 조사 결과가 있습니다.
이 글에서는 파이썬에서 환경변수를 설정하고 사용하는 모든 방법을 OS별로 완벽 정리했습니다.
환경변수가 뭐고 왜 필요한가?
환경변수란?
운영체제에 저장된 키-값 쌍으로, 프로그램이 실행 환경 정보에 접근할 수 있게 합니다.
# 환경변수 예시
API_KEY=abc123xyz789
DATABASE_URL=postgresql://localhost:5432/mydb
DEBUG=True
왜 사용해야 하나?
- 보안: 민감한 정보를 코드에서 분리
- 환경 분리: 개발/테스트/프로덕션 환경별 설정
- 팀 협업: 각자 다른 설정 사용 가능
- GitHub 안전: 인증정보 노출 방지
# ❌ 나쁜 예 (하드코딩)
API_KEY = "abc123xyz789" # GitHub에 올리면 큰일!
# ✅ 좋은 예 (환경변수)
import os
API_KEY = os.getenv('API_KEY') # 안전!
방법 1: Python에서 환경변수 읽기
os.environ 사용
import os
# 환경변수 가져오기 (없으면 KeyError)
api_key = os.environ['API_KEY']
# 환경변수 가져오기 (없으면 None)
api_key = os.environ.get('API_KEY')
# 기본값 지정
api_key = os.environ.get('API_KEY', 'default_key')
# 모든 환경변수 확인
print(os.environ)
os.getenv() 사용 (권장)
import os
# 기본 사용
api_key = os.getenv('API_KEY')
# 기본값 지정
debug = os.getenv('DEBUG', 'False')
# 타입 변환
port = int(os.getenv('PORT', 8000))
debug = os.getenv('DEBUG', 'False') == 'True'
차이점: os.getenv()는 없어도 에러 없음, os.environ[]은 KeyError 발생
실전 예제
import os
class Config:
"""설정 관리 클래스"""
# 필수 환경변수
SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY:
raise ValueError("SECRET_KEY 환경변수가 필요합니다!")
# 선택적 환경변수 (기본값 있음)
DEBUG = os.getenv('DEBUG', 'False') == 'True'
PORT = int(os.getenv('PORT', 5000))
HOST = os.getenv('HOST', '0.0.0.0')
# 데이터베이스 설정
DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite:///local.db')
# 외부 API
API_KEY = os.getenv('API_KEY')
API_URL = os.getenv('API_URL', '<https://api.example.com>')
# 사용
config = Config()
print(f"서버 실행: {config.HOST}:{config.PORT}")
print(f"디버그 모드: {config.DEBUG}")
방법 2: .env 파일 사용 (가장 추천!)
python-dotenv 설치
pip install python-dotenv
.env 파일 생성
프로젝트 루트에 .env 파일 생성:
# .env
API_KEY=abc123xyz789
DATABASE_URL=postgresql://localhost:5432/mydb
DEBUG=True
SECRET_KEY=your-secret-key-here
PORT=8000
# 주석도 가능
# EMAIL_PASSWORD=mypassword
Python에서 불러오기
from dotenv import load_dotenv
import os
# .env 파일 로드
load_dotenv()
# 환경변수 사용
api_key = os.getenv('API_KEY')
db_url = os.getenv('DATABASE_URL')
debug = os.getenv('DEBUG') == 'True'
print(f"API Key: {api_key}")
print(f"Database: {db_url}")
특정 경로의 .env 파일
from dotenv import load_dotenv
from pathlib import Path
# 프로젝트 루트 찾기
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)
# 또는 절대 경로
load_dotenv('/path/to/your/.env')
.env 파일 우선순위
from dotenv import load_dotenv
# override=False (기본값)
# OS 환경변수가 우선
load_dotenv()
# override=True
# .env 파일이 우선
load_dotenv(override=True)
실전 Django 예제
# settings.py
from pathlib import Path
from dotenv import load_dotenv
import os
# 프로젝트 루트
BASE_DIR = Path(__file__).resolve().parent.parent
# .env 로드
load_dotenv(BASE_DIR / '.env')
# Django 설정
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
DEBUG = os.getenv('DEBUG', 'False') == 'True'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', '5432'),
}
}
방법 3: Windows에서 환경변수 등록
GUI로 설정 (영구 적용)
1단계: 시스템 속성 열기
Win + R→sysdm.cpl입력 → 확인- 또는 “시스템 환경 변수 편집” 검색
2단계: 환경변수 버튼 클릭
3단계: 새로 만들기
- 사용자 변수: 현재 사용자만
- 시스템 변수: 모든 사용자
4단계: 입력
변수 이름: API_KEY
변수 값: abc123xyz789
CMD로 설정 (임시)
# 환경변수 설정 (현재 CMD 세션만)
set API_KEY=abc123xyz789
set DEBUG=True
# 확인
echo %API_KEY%
# Python 실행
python app.py
PowerShell로 설정
# 임시 설정 (현재 세션만)
$env:API_KEY = "abc123xyz789"
$env:DEBUG = "True"
# 확인
echo $env:API_KEY
# 영구 설정 (사용자)
[System.Environment]::SetEnvironmentVariable('API_KEY', 'abc123xyz789', 'User')
# 영구 설정 (시스템, 관리자 권한 필요)
[System.Environment]::SetEnvironmentVariable('API_KEY', 'abc123xyz789', 'Machine')
# Python 실행
python app.py
Python 스크립트로 설정
import os
# 현재 프로세스만 (임시)
os.environ['API_KEY'] = 'abc123xyz789'
# Windows에서 영구 설정
import subprocess
def set_windows_env_permanent(name, value):
"""Windows 환경변수 영구 설정"""
subprocess.run([
'setx', name, value
])
print(f"{name} 환경변수가 설정되었습니다.")
print("새 터미널에서 적용됩니다.")
# 사용
set_windows_env_permanent('API_KEY', 'abc123xyz789')
방법 4: Mac/Linux에서 환경변수 등록
터미널에서 임시 설정
# 환경변수 설정 (현재 세션만)
export API_KEY="abc123xyz789"
export DEBUG=True
# 확인
echo $API_KEY
# Python 실행
python app.py
영구 설정 (.bashrc / .zshrc)
Bash 사용자:
# ~/.bashrc 편집
nano ~/.bashrc
# 파일 끝에 추가
export API_KEY="abc123xyz789"
export DATABASE_URL="postgresql://localhost/mydb"
export DEBUG=True
# 저장 후 적용
source ~/.bashrc
Zsh 사용자 (Mac 기본):
# ~/.zshrc 편집
nano ~/.zshrc
# 파일 끝에 추가
export API_KEY="abc123xyz789"
export DATABASE_URL="postgresql://localhost/mydb"
# 저장 후 적용
source ~/.zshrc
.env 파일과 함께 사용
# .env 파일에서 환경변수 자동 로드
# ~/.bashrc 또는 ~/.zshrc에 추가
# 특정 프로젝트 디렉토리에서 자동 로드
if [ -f .env ]; then
export $(cat .env | xargs)
fi
Python 스크립트로 설정
import os
# 현재 프로세스만
os.environ['API_KEY'] = 'abc123xyz789'
# .bashrc에 추가 (영구 설정)
def set_bashrc_env(name, value):
"""~/.bashrc에 환경변수 추가"""
bashrc_path = os.path.expanduser('~/.bashrc')
with open(bashrc_path, 'a') as f:
f.write(f'\\nexport {name}="{value}"\\n')
print(f"{name} 환경변수가 ~/.bashrc에 추가되었습니다.")
print("터미널을 재시작하거나 'source ~/.bashrc'를 실행하세요.")
# 사용
set_bashrc_env('API_KEY', 'abc123xyz789')
방법 5: 환경별 설정 파일 (.env.dev, .env.prod)
여러 환경 관리
project/
├── .env.development
├── .env.production
├── .env.test
└── app.py
.env.development:
DEBUG=True
DATABASE_URL=sqlite:///dev.db
API_URL=http://localhost:8000
.env.production:
DEBUG=False
DATABASE_URL=postgresql://prod-server/db
API_URL=https://api.example.com
Python에서 선택적 로드
from dotenv import load_dotenv
import os
# 환경 선택 (기본값: development)
ENV = os.getenv('APP_ENV', 'development')
# 해당 환경 파일 로드
env_file = f'.env.{ENV}'
load_dotenv(env_file)
print(f"환경: {ENV}")
print(f"DEBUG: {os.getenv('DEBUG')}")
print(f"DB: {os.getenv('DATABASE_URL')}")
사용 방법
# 개발 환경
python app.py
# 프로덕션 환경
APP_ENV=production python app.py
# 테스트 환경
APP_ENV=test pytest
실전 프로젝트: 환경변수 관리 클래스
import os
from pathlib import Path
from typing import Optional, Any
from dotenv import load_dotenv
class EnvironmentConfig:
"""환경변수 관리 클래스"""
def __init__(self, env_file: Optional[str] = None):
"""
Args:
env_file: .env 파일 경로 (None이면 자동 탐색)
"""
self.env_file = env_file
self._load_env()
self._validate_required()
def _load_env(self):
"""환경변수 로드"""
if self.env_file:
load_dotenv(self.env_file)
else:
# 환경별 파일 자동 선택
env = os.getenv('APP_ENV', 'development')
env_file = f'.env.{env}'
if Path(env_file).exists():
load_dotenv(env_file)
print(f"✅ {env_file} 로드 완료")
elif Path('.env').exists():
load_dotenv('.env')
print("✅ .env 로드 완료")
else:
print("⚠️ .env 파일을 찾을 수 없습니다.")
def _validate_required(self):
"""필수 환경변수 확인"""
required = ['SECRET_KEY']
missing = [var for var in required if not os.getenv(var)]
if missing:
raise EnvironmentError(
f"필수 환경변수가 없습니다: {', '.join(missing)}"
)
def get(self, key: str, default: Any = None, required: bool = False) -> Any:
"""
환경변수 가져오기
Args:
key: 환경변수 이름
default: 기본값
required: 필수 여부
"""
value = os.getenv(key, default)
if required and value is None:
raise EnvironmentError(f"필수 환경변수 {key}가 설정되지 않았습니다.")
return value
def get_bool(self, key: str, default: bool = False) -> bool:
"""Boolean 환경변수"""
value = os.getenv(key, str(default))
return value.lower() in ('true', '1', 'yes', 'on')
def get_int(self, key: str, default: int = 0) -> int:
"""Integer 환경변수"""
value = os.getenv(key, str(default))
try:
return int(value)
except ValueError:
return default
def get_list(self, key: str, separator: str = ',', default: list = None) -> list:
"""리스트 환경변수 (쉼표 구분)"""
if default is None:
default = []
value = os.getenv(key)
if not value:
return default
return [item.strip() for item in value.split(separator)]
def get_dict(self, prefix: str) -> dict:
"""특정 접두사로 시작하는 모든 환경변수"""
return {
key.replace(prefix, '', 1): value
for key, value in os.environ.items()
if key.startswith(prefix)
}
def set_temp(self, key: str, value: str):
"""임시 환경변수 설정 (현재 프로세스만)"""
os.environ[key] = value
def display_config(self, mask_secrets: bool = True):
"""현재 설정 출력"""
print("\\n=== 환경 설정 ===")
secret_keys = ['KEY', 'SECRET', 'PASSWORD', 'TOKEN']
for key, value in sorted(os.environ.items()):
# 시스템 환경변수 제외
if key.startswith('_') or key in ['PATH', 'HOME', 'USER']:
continue
# 민감한 정보 마스킹
if mask_secrets and any(secret in key.upper() for secret in secret_keys):
display_value = '*' * 8
else:
display_value = value
print(f"{key}: {display_value}")
# 사용 예제
if __name__ == '__main__':
# 설정 로드
config = EnvironmentConfig()
# 기본 사용
api_key = config.get('API_KEY', required=True)
debug = config.get_bool('DEBUG', default=False)
port = config.get_int('PORT', default=5000)
# 리스트 환경변수
# ALLOWED_HOSTS=localhost,127.0.0.1,example.com
hosts = config.get_list('ALLOWED_HOSTS')
# 접두사로 검색
# AWS_ACCESS_KEY=xxx, AWS_SECRET_KEY=yyy
aws_config = config.get_dict('AWS_')
# 설정 출력
config.display_config()
# 임시 설정
config.set_temp('TEMP_VAR', 'temp_value')
.gitignore 설정 (중요!)
.env 파일 제외하기
# .gitignore
# 환경변수 파일
.env
.env.local
.env.*.local
# 환경별 설정 (선택적)
# .env.development
# .env.production
.env.test
.env.example 제공
# .env.example
# 팀원들을 위한 환경변수 템플릿
# API 설정
API_KEY=your-api-key-here
API_URL=https://api.example.com
# 데이터베이스
DATABASE_URL=postgresql://user:password@localhost/dbname
# 디버그 모드
DEBUG=True
# 서버 설정
PORT=8000
HOST=0.0.0.0
Docker에서 환경변수 사용
Dockerfile
FROM python:3.11
WORKDIR /app
# 환경변수 설정
ENV PYTHONUNBUFFERED=1
ENV APP_ENV=production
# 복사 및 설치
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# 환경변수 파일 제외 (.dockerignore 사용)
# .env는 런타임에 주입
CMD ["python", "app.py"]
docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
- DEBUG=False
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- SECRET_KEY=${SECRET_KEY} # 호스트 환경변수 사용
env_file:
- .env.production # 파일에서 로드
ports:
- "8000:8000"
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
실행
# .env 파일 또는 환경변수 설정
export SECRET_KEY=your-secret-key
export DB_PASSWORD=your-db-password
# Docker Compose 실행
docker-compose up
자주 하는 실수와 해결법
실수 1: .env 파일을 Git에 커밋
# ❌ 이미 커밋한 경우
# Git 히스토리에서 완전히 제거 필요
# 파일 삭제 및 히스토리 제거
git filter-branch --force --index-filter \\
"git rm --cached --ignore-unmatch .env" \\
--prune-empty --tag-name-filter cat -- --all
# 강제 푸시
git push origin --force --all
실수 2: 환경변수 타입 변환 없음
# ❌ 문자열로 비교
DEBUG = os.getenv('DEBUG') # "False" (문자열)
if DEBUG: # 항상 True!
print("디버그 모드")
# ✅ Boolean 변환
DEBUG = os.getenv('DEBUG', 'False') == 'True'
if DEBUG:
print("디버그 모드")
# ✅ 또는 헬퍼 함수
def get_bool(key, default=False):
value = os.getenv(key, str(default))
return value.lower() in ('true', '1', 'yes')
DEBUG = get_bool('DEBUG')
실수 3: .env 파일 로드 안 됨
# ❌ load_dotenv() 호출 안 함
import os
api_key = os.getenv('API_KEY') # None
# ✅ 먼저 로드
from dotenv import load_dotenv
load_dotenv()
import os
api_key = os.getenv('API_KEY') # 정상
실수 4: 경로 문제
# ❌ 상대 경로 문제
load_dotenv('.env') # 현재 실행 위치에 따라 다름
# ✅ 절대 경로 사용
from pathlib import Path
env_path = Path(__file__).parent / '.env'
load_dotenv(env_path)
실수 5: 환경변수 덮어쓰기
# ❌ OS 환경변수를 덮어씀
load_dotenv(override=True)
# ✅ OS 환경변수 우선
load_dotenv(override=False) # 기본값
보안 모범 사례
1. 절대 하드코딩하지 말 것
# ❌ 절대 금지
API_KEY = "abc123xyz789"
PASSWORD = "mypassword"
# ✅ 환경변수 사용
API_KEY = os.getenv('API_KEY')
PASSWORD = os.getenv('PASSWORD')
2. .env.example 제공
# .env.example
# 실제 값은 .env에 입력
API_KEY=your-api-key-here
DATABASE_URL=your-database-url
SECRET_KEY=generate-random-secret-key
3. 민감한 정보는 별도 관리
# 프로덕션에서는 AWS Secrets Manager,
# Azure Key Vault 등 사용 권장
import boto3
def get_secret(secret_name):
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId=secret_name)
return response['SecretString']
# 환경변수 또는 Secrets Manager
if os.getenv('APP_ENV') == 'production':
API_KEY = get_secret('prod/api_key')
else:
API_KEY = os.getenv('API_KEY')
4. 환경변수 검증
# 필수 환경변수 확인
REQUIRED_VARS = ['SECRET_KEY', 'DATABASE_URL', 'API_KEY']
missing = [var for var in REQUIRED_VARS if not os.getenv(var)]
if missing:
raise EnvironmentError(
f"필수 환경변수가 없습니다: {', '.join(missing)}\\n"
f".env 파일을 확인하거나 환경변수를 설정하세요."
)
마치며: 환경변수는 필수입니다
환경변수는 단순히 편의 기능이 아니라 보안과 유지보수의 핵심입니다. 한 번 익혀두면 모든 프로젝트에서 활용할 수 있습니다.
기억할 핵심 3가지:
- 절대 하드코딩하지 말 것 – API 키, 비밀번호 등
- .env 파일 사용 – python-dotenv 권장
- .gitignore 필수 – .env 파일은 절대 커밋 금지
처음엔 번거로워 보이지만, 한 번 세팅하면 평생 안전합니다. 특히 팀 프로젝트나 오픈소스에서는 필수입니다!
이 글이 도움되셨다면 북마크하고, 보안에 관심 있는 동료에게 공유해주세요!