
Environment는 단순히 “환경”이라는 뜻이 아니다.
프로그램이 실행될 때 주변에서 영향을 주는 모든 조건을 의미한다.
즉, 코드는 같아도 환경이 바뀌면 동작이 달라질 수 있는 값들이다.
처음 개발할 때 많은 사람들이 이렇게 시작한다.
DB_HOST = "192.168.1.100" DB_USER = "admin" DB_PASSWORD = "supersecret123" DB_NAME = "my_service"
실행은 잘 되지만...
이 코드가 GitHub에 올라가는 순간?
| 환경 | DB 주소 | 목적 |
|---|---|---|
| 개발 | localhost | 로컬 테스트 |
| 테스트 | test.company.com | QA 검증 |
| 운영 | prod.company.com | 실서비스 |
.env가 없으면?
# 개발 DB_HOST = "localhost" # 테스트 DB_HOST = "test.company.com" # 운영 DB_HOST = "prod.company.com"
👉 환경 바뀔 때마다 코드를 수정한다.
👉 코드를 수정한다는 건 새 버그를 만들 기회를 추가하는 것이다.
김철수:
DB_HOST = "chulsoo-pc"
↓
커밋
jay:
코드 받음 → 접속 실패 😡
DB_HOST = "jay-pc"
↓
커밋
김철수:
코드 받음 → 접속 실패 😡
이 상황의 본질은 이것이다.
“개인 환경 설정이 코드에 섞여 있다”
결과는?
commit 1: 로그인 기능 구현 commit 2: DB 주소 변경 (개발) commit 3: 버그 수정 commit 4: DB 주소 변경 (테스트) commit 5: 새 기능 추가 commit 6: DB 주소 변경 (운영)
나중에 이 히스토리를 보면?
아무도 알 수 없다.
# 개발용 설정 그대로 운영 서버에 배포 DB_HOST = "localhost"
운영 서버에서 localhost는?
실제 현업에서 매년 반복되는 사고..
| 문제 | 원인 | 결과 |
|---|---|---|
| 보안 사고 | 비밀정보가 코드에 있음 | 해킹, 정보 유출 |
| 환경 관리 실패 | 환경마다 코드 수정 | 버그 증가 |
| 협업 충돌 | 개인 설정이 커밋됨 | 팀 생산성 하락 |
| 이력 오염 | 설정과 로직 혼합 | 변경 추적 불가 |
| 운영 장애 | 환경 분리 실패 | 서비스 중단 |
코드와 설정은 절대 섞지 않는다
이 원칙을 지키기 위해 등장한 것이 바로 .env 파일과 환경변수 관리다.
.env.example)환경변수는 코드 밖에서 프로그램에게 주는 설정값이다.
중요한 것은..
둘을 섞으면?
배포할 때마다 코드 수정 → 커밋 오염 → 실수 → 장애
(콤보 오픈)
| 구분 | 예시 | 특징 | 관리 전략 |
|---|---|---|---|
| Config | DB_HOST, PORT, LOG_LEVEL | 노출돼도 치명적이지 않은 값 | .env / 설정파일 / 환경변수 |
| Secret | DB_PASSWORD, API_KEY, JWT_SECRET | 노출되면 서비스 끝장날 수 있음 | Secrets Manager / Vault / K8s Secret |
환경변수 관리 방식 선택 흐름
[시작]
|
v
프로젝트 규모는?
|
+--> 개인/소규모 ---------------------+
| |
| 보안 중요?
| |
| +-----------+-----------+
| | |
| v v
| 낮음(.env) 높음(Secrets Manager)
|
+--> 팀/중간 규모 -----------------------------+
| |
| 배포 형태?
| |
| +--------------------+--------------------+
| | |
| v v
| 서버 직접 배포(OS env) 컨테이너(Docker/K8s)
|
+--> 대규모/규정 빡셈 ---------------------------------> Vault / 중앙 비밀관리
| 방법 | 추천 상황 | 장점 | 단점 | 보안 |
|---|---|---|---|---|
| .env | 로컬 개발, 소규모 | 세팅 빠름, 직관적 | 유출 위험(실수로 커밋) | 낮음 |
| OS 환경변수 | 서버 직접 운영 | 파일 없이 관리, CI/CD 연계 쉬움 | 서버 여러 대면 반복 작업 | 중간 |
| 설정 파일(JSON/YAML) | 계층 구조 설정 필요 | 구조 표현 강함 | Secret 넣으면 바로 지뢰 | 낮음~중간 |
| Docker/K8s env | 컨테이너 배포 | 배포 파이프라인과 찰떡 | Secret 관리 따로 해야 함 | 중간~높음 |
| Secrets Manager | 클라우드 + 보안 중요 | 권한제어/로깅/회전(rotate) | 비용/세팅 필요 | 높음 |
| Vault | 대규모 조직 | 중앙 통제, 강력한 정책 | 운영 난이도 높음 | 매우 높음 |
.env는 무조건 .gitignore.env.example 만들어서 키 목록만 공유.env, 운영은 OS env / Secret로 주입하는 식으로 역할 분리# .env (절대 커밋하지 말기)
APP_ENV=local
DB_HOST=localhost
DB_PORT=5432
DB_USER=admin
DB_PASSWORD=supersecret
# .env.example (이건 커밋 OK - 값은 더미로)
APP_ENV=local
DB_HOST=localhost
DB_PORT=5432
DB_USER=admin
DB_PASSWORD=CHANGEME
# Python에서 읽기 (python-dotenv)
# pip install python-dotenv
import os
from dotenv import load_dotenv
load_dotenv()
DB_HOST = os.getenv("DB_HOST")
DB_PASSWORD = os.getenv("DB_PASSWORD")
print(DB_HOST)
# Linux/Mac
export APP_ENV=prod
export DB_HOST="prod.db.internal"
export DB_PASSWORD="xxxxx"
# 영구 적용(예: bash)
echo 'export DB_HOST="prod.db.internal"' >> ~/.bashrc
source ~/.bashrc
운영에서 이 방식이 좋은 이유는 단순하다.
docker run \
-e APP_ENV=prod \
-e DB_HOST=prod.db.internal \
-e DB_PASSWORD=xxxxx \
myapp:latest
--env-file 사용:
docker run --env-file ./production.env myapp:latest
(개념)
ConfigMap = 일반 설정
Secret = 민감정보
K8s에서 컨테이너로 값 주입 흐름:
[ConfigMap/Secret]
|
v
(Deployment env)
|
v
[Container ENV]
|
v
app이 os.getenv()로 읽음
# secret.yaml
# 주의: 기본 K8s Secret은 "암호화"가 아니라 base64 인코딩 수준임
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
password: c3VwZXJzZWNyZXQ= # "supersecret" (base64)
# deployment.yaml (Secret 주입)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
Secret을 파일(.env)로 들고 다니면 항상 “실수로 커밋” 지뢰가 남는다.
Secrets Manager 계열은 보통 아래 3개가 핵심이다:
# (예시) AWS Secrets Manager - 개념 코드
# boto3 사용
import json
import boto3
client = boto3.client("secretsmanager", region_name="ap-northeast-2")
response = client.get_secret_value(SecretId="my-db-credentials")
secrets = json.loads(response["SecretString"])
db_password = secrets["password"]
print(db_password)
추천 우선순위
1) 실행 시 주입된 환경변수 (가장 우선)
2) Secrets Manager/Vault에서 가져온 값
3) .env (로컬 개발 편의)
4) 코드 기본값(default)
이렇게 해두면:
- 운영: 1,2로만 굴러감
- 로컬: 3으로 편하게 개발
- 누락: 4로 최소한의 기본 동작
1) 비밀번호/API 키를 코드에 하드코딩
2) .env를 커밋 (또는 스크린샷/블로그에 노출)
3) 설정값 바꾸려고 운영 코드를 수정해서 배포
코드는 “무엇을” 담고, 설정은 “어디서/어떻게” 담는다.
둘을 섞는 순간, 장애는 “언젠가”가 아니라 “곧” 온다.
.env는 gitignore 처리했나?.env.example로 하고 있나?