파이썬 환경변수 등록/사용 완벽 가이드

API 키나 데이터베이스 비밀번호를 코드에 직접 쓰다가 GitHub에 올려서 당황한 경험, 있으신가요? 저도 그랬습니다. 환경변수를 쓰면 이런 실수를 막을 수 있다는 걸 나중에 알게 됐죠. 실제로 보안 사고의 74%가 하드코딩된 인증정보 때문이라는 조사 결과가 있습니다.

이 글에서는 파이썬에서 환경변수를 설정하고 사용하는 모든 방법을 OS별로 완벽 정리했습니다.

환경변수가 뭐고 왜 필요한가?

환경변수란?

운영체제에 저장된 키-값 쌍으로, 프로그램이 실행 환경 정보에 접근할 수 있게 합니다.

# 환경변수 예시
API_KEY=abc123xyz789
DATABASE_URL=postgresql://localhost:5432/mydb
DEBUG=True

왜 사용해야 하나?

  1. 보안: 민감한 정보를 코드에서 분리
  2. 환경 분리: 개발/테스트/프로덕션 환경별 설정
  3. 팀 협업: 각자 다른 설정 사용 가능
  4. 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 + Rsysdm.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가지:

  1. 절대 하드코딩하지 말 것 – API 키, 비밀번호 등
  2. .env 파일 사용 – python-dotenv 권장
  3. .gitignore 필수 – .env 파일은 절대 커밋 금지

처음엔 번거로워 보이지만, 한 번 세팅하면 평생 안전합니다. 특히 팀 프로젝트나 오픈소스에서는 필수입니다!

이 글이 도움되셨다면 북마크하고, 보안에 관심 있는 동료에게 공유해주세요!


참고 자료

댓글 남기기