1. 30분 만에 AWS 해킹당한 썰
제 인생 최악의 실수는 첫 프로젝트를 GitHub에 올린 날 일어났습니다.
AWS_ACCESS_KEY를 코드 안에 그대로 박아놓고 커밋해버린 것입니다.
정확히 30분 뒤, 아마존에서 이메일이 왔습니다. "당신의 계정이 비정상적인 활동을 감지했습니다."
들어가 보니 누군가 제 계정으로 시가 500만 원어치의 채굴용 인스턴스를 돌리고 있었습니다.
이게 바로 우리가 환경 변수(Environment Variables)를 써야 하는 가장 강력한 이유입니다. 코드(Code)와 설정(Config)은 반드시 분리되어야 합니다. 코드는 Git에 올라가 전 세계에 공유되지만, 설정(비밀번호, API 키)은 절대 공개되면 안 되기 때문입니다.
2. 환경 변수의 우선순위 (Precedence)
초보자가 자주 하는 실수는 환경 변수가 어디서 오는지 헷갈리는 것입니다. Node.js나 Python 앱이 실행될 때, 변수는 여러 곳에서 주입될 수 있습니다. 우선순위는 보통 다음과 같습니다 (아래로 갈수록 덮어씀):
- OS 기본 변수:
/etc/environment등에 설정된 전역 변수 - .env 파일:
dotenv라이브러리로 로드하는 파일 - Shell 명령어:
export API_KEY=abc로 설정한 값 - CLI 인자:
API_KEY=abc node server.js처럼 실행 시 앞에 붙이는 값
팁: 로컬 개발할 때는 .env.local 같은 파일을 써서 Git이 추적하지 않게 하고, 팀원들과 공유해야 할 기본값은 .env.example에 넣어두는 것이 국룰입니다.
3. 시크릿의 무분별한 확산 (Secret Sprawl)
회사가 커지고 마이크로서비스(MSA)를 도입하면 새로운 지옥이 열립니다. 바로 Secret Sprawl(시크릿 확산)입니다.
서비스가 100개면 DB 비밀번호도 100군데에 흩어져 있습니다.
개발자 A의 로컬 PC .env 파일에, 개발자 B의 슬랙 DM에, 심지어 노션 문서 어딘가에 비밀번호가 평문으로 돌아다닙니다.
이것을 막기 위해 GitGuardian 같은 도구를 CI 파이프라인에 심어둬야 합니다.
누군가 실수로 커밋에 비밀번호 형태의 문자열(예: sk_live_...)을 포함시키면, 커밋 자체를 막아버리는(Pre-commit Hook) 것입니다.
"실수하고 나서 수습"하는 게 아니라 "실수 자체를 못 하게" 막아야 합니다.
4. Docker와 Kubernetes에서의 주입
요즘은 서버에 직접 들어가서 .env 파일을 수정하지 않습니다. 컨테이너 환경이기 때문이죠.
Docker
docker run 명령어를 쓸 때 -e 옵션을 줍니다.
docker run -e DB_PASSWORD=secret my-app
하지만 변수가 많으면 --env-file .env 옵션으로 파일 통째로 주입합니다.
Kubernetes (K8s)
쿠버네티스에서는 ConfigMap과 Secret 리소스를 사용합니다.
- ConfigMap: DB 호스트 주소, 로그 레벨 같은 비민감 정보.
- Secret: DB 비밀번호, API 키 같은 민감 정보. (Base64로 인코딩되어 저장됨)
Pod를 띄울 때 이 Secret을 환경 변수로 주입(Injection)해서 사용합니다. 애플리케이션 입장에서는 이게 파일에서 왔는지, K8s가 넣어줬는지 알 필요가 없습니다. 그냥 process.env로 읽으면 되니까요. 이것이 바로 인프라 독립적인(Infrastructure Agnostic) 애플리케이션 설계입니다.
5. 12-Factor App 원칙 - 설정(Config)
모던 애플리케이션 배포의 바이블인 The 12-Factor App에서는 설정을 이렇게 정의합니다.
"설정을 코드에서 엄격하게 분리하라. 코드는 지금 당장 오픈소스로 공개해도 아무런 문제가 없어야 한다."
만약 코드를 GitHub에 올리기 전에 "잠깐, 이 파일 지워야 해"라고 생각한다면, 당신은 원칙을 어긴 것입니다. 설정은 배포 환경(Development, Staging, Production)에 따라 달라지는 모든 것을 말합니다. 데이터베이스 주소, 외부 서비스 URL, 캐시 설정 등이 여기에 속합니다. 이 모든 것을 환경 변수로 관리해야만, 빌드된 아티팩트(Docker Image) 하나를 가지고 여러 환경에 똑같이 배포(Build Once, Deploy Anywhere)할 수 있습니다.
6. 실제적인 시크릿 관리: AWS Secrets Manager
프로덕션 환경에서는 환경 변수조차 위험할 수 있습니다.
DevOps 엔지니어가 docker inspect 명령어를 치면 환경 변수가 평문으로 다 보이기 때문입니다.
그리고 데이터베이스 비밀번호를 3개월마다 바꿔야 한다면? 수많은 서버를 다 재시작해야 할까요?
그래서 대규모 시스템에서는 AWS Secrets Manager나 HashiCorp Vault 같은 전용 시크릿 저장소를 씁니다.
- 애플리케이션이 부팅될 때 Secrets Manager API를 호출합니다.
- 메모리에 시크릿을 로드해서 사용합니다.
- 환경 변수에는 아무것도 남기지 않습니다.
이 방식의 장점은 자동 로테이션(Rotation)이 가능하다는 것입니다. AWS가 알아서 DB 비밀번호를 바꾸고, 애플리케이션은 API로 바뀐 비밀번호를 받아오면 되니까, 서버 중단 없이 보안을 강화할 수 있습니다.
7. 프론트엔드에서의 환경 변수 주의점
React나 Next.js 같은 프론트엔드 개발 시 주의할 점이 있습니다.
브라우저로 전송되는 자바스크립트 번들에는 비밀이 있을 수 없습니다.
REACT_APP_API_KEY나 NEXT_PUBLIC_ANALYTICS_ID 같은 변수들은 빌드 타임(Build Time)에 코드에 치환(Replacement)되어 들어갑니다.
개발자 도구(F12)를 열어서 소스 코드를 보면 다 보입니다.
그러니 프론트엔드 환경 변수에는 절대 "AWS Secret Key" 같은 걸 넣으면 안 됩니다. 오직 "공개되어도 상관없는 정보(Public Key, API URL)"만 넣어야 합니다.
8. 자주 묻는 질문 (FAQ)
Q: 실수로 .env 파일을 GitHub에 올려버렸어요. 지우면 되나요?
A: 아니요! 커밋을 revert 하거나 파일을 지워도, Git의 커밋 히스토리(history)에는 영원히 남아있습니다. 해커들은 이 히스토리를 뒤집니다.
반드시 BFG Repo-Cleaner 같은 도구로 히스토리 자체를 세탁하거나, 더 안전하게는 노출된 모든 키를 폐기(Revoke)하고 재발급 받아야 합니다. 키를 바꾸는 게 가장 확실한 방법입니다.
Q: 프로덕션 환경에서도 .env 파일을 쓰면 안 되나요?
A: 써도 되지만 권장하지 않습니다. .env 파일은 파일 시스템에 평문으로 존재하므로, 서버가 해킹당하면 바로 털립니다. AWS Param Store나 Secrets Manager 같은 주입식 방식을 쓰는 것이 훨씬 안전합니다.
Q: ENV_VAR vs config.json 중 뭐가 낫나요?
A: config.json은 구조화된 데이터를 표현하기 좋지만, 배포 환경마다 파일을 따로 만들어야 하므로 관리가 복잡합니다. 반면 환경 변수는 어디서든 오버라이드하기 쉬워서 Docker/K8s 환경 표준입니다. 12-Factor App을 따른다면 환경 변수가 정답입니다.