🔐 환경변수 관리
📖 정의
**환경변수(Environment Variables)**는 애플리케이션의 동작을 제어하는 설정값으로, 코드 외부에서 관리됩니다. API 키, 데이터베이스 비밀번호, 서버 포트 등 민감한 정보를 코드에 직접 작성하지 않고, .env 파일이나 배포 플랫폼의 설정으로 관리합니다. 개발(dev), 스테이징(staging), 프로덕션(production) 환경마다 다른 값을 사용할 수 있습니다.
🎯 비유로 이해하기
옷장의 보석 금고
환경변수를 금고에 비유하면:
코드 = 공개된 옷장
├─ 누구나 볼 수 있음
└─ GitHub에 업로드됨
환경변수 = 비밀 금고
├─ 본인만 비밀번호 알고 있음
├─ 코드와 분리
└─ 절대 공개하 지 않음
API 키 = 보석
├─ 코드에 직접 놓으면: 도난 위험!
└─ 금고에 보관하면: 안전!
.env 파일 = 금고 열쇠
├─ 로컬에만 보관
├─ .gitignore로 보호
└─ 절대 GitHub에 올리지 않음
레스토랑 레시피
공개 레시피 = 코드
├─ 요리 순서
├─ 재료 목록
└─ 조리 방법
비밀 소스 = 환경변수
├─ 정확한 비율
├─ 특별한 재료
└─ 경쟁사가 모르는 정보
환경별 차이:
- 개발 주방: 저렴한 재료로 테스트
- 프로덕션 주방: 최고급 재료 사용
- 각 주방의 비밀 소스는 다름!
⚙️ 작동 원리
1. 환경변수의 필요성
// ❌ 나쁜 예 (하드코딩)
const API_KEY = 'sk_live_abc123xyz789'; // GitHub에 노출!
const DATABASE_URL = 'mongodb://admin:password123@server.com/db';
// 문제점:
// 1. 코드가 GitHub에 공개되면 API 키 노출
// 2. 환경마다 값을 바꾸려면 코드 수정 필요
// 3. 팀원들과 공유 시 보안 위험
// 4. 키 변경 시 코드 재배포 필요
// ✅ 좋은 예 (환경변수)
const API_KEY = process.env.API_KEY;
const DATABASE_URL = process.env.DATABASE_URL;
// 장점:
// 1. 코드에 비밀 정보 없음
// 2. 환경별로 다른 값 사용
// 3. 키 변경 시 코드 수정 불필요
// 4. .env 파일로 로컬 관리
2. 환경변수 로딩 과정
1. 애플리케이션 시작
└─ node server.js
2. dotenv 라이브러리 로드
└─ require('dotenv').config()
3. .env 파일 읽기
└─ API_KEY=abc123
4. process.env에 주입
└─ process.env.API_KEY = 'abc123'
5. 코드에서 접근
└─ const key = process.env.API_KEY
6. 사용
└─ API 호출 시 key 사용
3. 환경별 설정
개발 환경 (Development)
├─ .env.local 또는 .env.development
├─ 로컬 데이터베이스
├─ 테스트용 API 키
├─ 디버그 모드 ON
└─ localhost:3000
스테이징 환경 (Staging)
├─ 배포 플랫폼 환경변수
├─ 테스트 데이터베이스
├─ 샌드박스 API 키
├─ 프로덕션과 유사한 환경
└─ staging.myapp.com
프로덕션 환경 (Production)
├─ 배포 플랫폼 환경변수
├─ 실제 데이터베이스
├─ 실제 API 키
├─ 에러 로깅 ON
└─ myapp.com
💡 실제 예시
Step 1: .env 파일 기본 사용법
# 프로젝트 생성
mkdir env-demo
cd env-demo
npm init -y
# dotenv 패키지 설치
npm install dotenv
# 추가 패키지 (API 호출 예제용)
npm install express axios
# .env 파일 생성 (루트 디 렉토리)
cat > .env << 'EOF'
# Server Configuration
PORT=3000
NODE_ENV=development
# Database
DATABASE_URL=mongodb://localhost:27017/myapp
DATABASE_NAME=myapp_dev
# API Keys
OPENAI_API_KEY=sk-abc123xyz789
STRIPE_SECRET_KEY=sk_test_abc123
STRIPE_PUBLIC_KEY=pk_test_xyz789
# External Services
SENDGRID_API_KEY=SG.abc123xyz789
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_REGION=us-east-1
# JWT
JWT_SECRET=my-super-secret-jwt-key-change-in-production
JWT_EXPIRE=7d
# URLs
FRONTEND_URL=http://localhost:3000
BACKEND_URL=http://localhost:5000
# Feature Flags
ENABLE_LOGGING=true
ENABLE_ANALYTICS=false
MAX_FILE_SIZE=5242880
EOF
# ⚠️ 중요: .gitignore에 추가!
cat > .gitignore << 'EOF'
node_modules/
.env
.env.local
.env.*.local
*.log
.DS_Store
EOF
// server.js
require('dotenv').config(); // 맨 위에 추가!
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// 환경변수 사용
app.get('/', (req, res) => {
res.json({
message: 'Environment Variables Demo',
environment: process.env.NODE_ENV,
port: PORT,
databaseName: process.env.DATABASE_NAME,
// ⚠️ 주의: 실제로는 비밀 키를 응답으로 보내지 마세요!
jwtConfigured: !!process.env.JWT_SECRET,
loggingEnabled: process.env.ENABLE_LOGGING === 'true'
});
});
// API 키 사용 예시
app.get('/api/data', async (req, res) => {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
return res.status(500).json({
error: 'API key not configured'
});
}
// API 호출 (실제 예시)
try {
// const response = await callExternalAPI(apiKey);
res.json({
message: 'API key is configured',
keyPrefix: apiKey.substring(0, 7) + '...' // 일부만 표시
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(PORT, () => {
console.log(`✅ Server running on port ${PORT}`);
console.log(`📝 Environment: ${process.env.NODE_ENV}`);
console.log(`🗄️ Database: ${process.env.DATABASE_NAME}`);
});
# 서버 실행
node server.js
# 출력:
# ✅ Server running on port 3000
# 📝 Environment: development
# 🗄️ Database: myapp_dev
# 테스트
curl http://localhost:3000
# {
# "message": "Environment Variables Demo",
# "environment": "development",
# "port": "3000",
# ...
# }
Step 2: 환경별 설정 파일
# 여러 환경별 .env 파일 생성
# 1. 개발 환경
cat > .env.development << 'EOF'
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp_dev
API_BASE_URL=http://localhost:3000
DEBUG=true
ENABLE_LOGGING=true
EOF
# 2. 테스트 환경
cat > .env.test << 'EOF'
NODE_ENV=test
PORT=3001
DATABASE_URL=mongodb://localhost:27017/myapp_test
API_BASE_URL=http://localhost:3001
DEBUG=true
ENABLE_LOGGING=false
EOF
# 3. 프로덕션 환경 (예시)
cat > .env.production.example << 'EOF'
NODE_ENV=production
PORT=8080
DATABASE_URL=mongodb://user:password@cluster.mongodb.net/myapp_prod
API_BASE_URL=https://api.myapp.com
DEBUG=false
ENABLE_LOGGING=true
SENTRY_DSN=https://abc123@sentry.io/123456
EOF
# .gitignore 업데이트
cat >> .gitignore << 'EOF'
.env
.env.local
.env.development
.env.test
.env.production
.env.*.local
EOF
# ✅ .example 파일은 커밋 (팀원들이 참고)
# ❌ 실제 .env 파일은 절대 커밋하지 않음!
// config/env.js
// 환경변수 관리 모듈
require('dotenv').config({
path: `.env.${process.env.NODE_ENV || 'development'}`
});
const config = {
// 서버 설정
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
// 데이터베이스
database: {
url: process.env.DATABASE_URL,
name: process.env.DATABASE_NAME
},
// API 키
apiKeys: {
openai: process.env.OPENAI_API_KEY,
stripe: {
secret: process.env.STRIPE_SECRET_KEY,
public: process.env.STRIPE_PUBLIC_KEY
},
sendgrid: process.env.SENDGRID_API_KEY
},
// JWT
jwt: {
secret: process.env.JWT_SECRET,
expire: process.env.JWT_EXPIRE || '7d'
},
// AWS
aws: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION || 'us-east-1'
},
// URLs
urls: {
frontend: process.env.FRONTEND_URL,
backend: process.env.BACKEND_URL
},
// Feature Flags
features: {
logging: process.env.ENABLE_LOGGING === 'true',
analytics: process.env.ENABLE_ANALYTICS === 'true',
debug: process.env.DEBUG === 'true'
},
// 파일 크기 제한 (바이트 → MB)
maxFileSize: parseInt(process.env.MAX_FILE_SIZE || '5242880')
};
// 필수 환경변수 검증
const requiredEnvVars = [
'DATABASE_URL',
'JWT_SECRET'
];
requiredEnvVars.forEach(varName => {
if (!process.env[varName]) {
console.error(`❌ Missing required environment variable: ${varName}`);
process.exit(1);
}
});
// 개발 환경에서만 전체 설정 출력
if (config.nodeEnv === 'development') {
console.log('📝 Configuration loaded:');
console.log(JSON.stringify(config, null, 2));
}
module.exports = config;
// server.js (업데이트)
const express = require('express');
const config = require('./config/env');
const app = express();
app.get('/', (req, res) => {
res.json({
message: 'Environment Configuration',
environment: config.nodeEnv,
port: config.port,
features: config.features
});
});
app.listen(config.port, () => {
console.log(`✅ Server running on port ${config.port}`);
console.log(`📝 Environment: ${config.nodeEnv}`);
});
# 환경별 실행
# 개발 환경
NODE_ENV=development node server.js
# 테스트 환경
NODE_ENV=test node server.js
# 프로덕션 환경
NODE_ENV=production node server.js
# package.json에 스크립트 추가
cat > package.json << 'EOF'
{
"name": "env-demo",
"scripts": {
"start": "node server.js",
"dev": "NODE_ENV=development nodemon server.js",
"test": "NODE_ENV=test jest",
"prod": "NODE_ENV=production node server.js"
},
"dependencies": {
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
EOF
# 실행
npm run dev # 개발
npm test # 테스트
npm start # 기본
Step 3: React/Frontend 환경변수
# Create React App
npx create-react-app my-app
cd my-app
# .env 파일 생성 (public 폴더 아님, 루트!)
cat > .env << 'EOF'
# ⚠️ React는 REACT_APP_ 접두사 필수!
REACT_APP_API_URL=http://localhost:5000
REACT_APP_API_KEY=abc123xyz789
REACT_APP_GOOGLE_MAPS_KEY=AIzaSyABC123XYZ789
REACT_APP_ENABLE_ANALYTICS=true
# ❌ 이렇게 하면 안 됨 (접두사 없음)
# API_URL=http://localhost:5000 # undefined 됨!
EOF
# 환경별 파일
cat > .env.development << 'EOF'
REACT_APP_API_URL=http://localhost:5000
REACT_APP_DEBUG=true
EOF
cat > .env.production << 'EOF'
REACT_APP_API_URL=https://api.myapp.com
REACT_APP_DEBUG=false
EOF
// src/config.js
const config = {
apiUrl: process.env.REACT_APP_API_URL,
apiKey: process.env.REACT_APP_API_KEY,
googleMapsKey: process.env.REACT_APP_GOOGLE_MAPS_KEY,
enableAnalytics: process.env.REACT_APP_ENABLE_ANALYTICS === 'true',
isDevelopment: process.env.NODE_ENV === 'development'
};
// 검증
if (!config.apiUrl) {
console.error('❌ REACT_APP_API_URL is not defined!');
}
export default config;
// src/App.js
import React, { useEffect, useState } from 'react';
import config from './config';
function App() {
const [data, setData] = useState(null);
useEffect(() => {
// 환경변수 사용
fetch(`${config.apiUrl}/api/data`, {
headers: {
'X-API-Key': config.apiKey
}
})
.then(res => res.json())
.then(data => setData(data))
.catch(err => console.error(err));
}, []);
return (
<div>
<h1>My App</h1>
<p>Environment: {process.env.NODE_ENV}</p>
<p>API URL: {config.apiUrl}</p>
<p>Debug Mode: {config.isDevelopment ? 'ON' : 'OFF'}</p>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
export default App;
# 빌드 시 환경변수 주입됨
npm run build
# build/static/js/main.js에 값이 포함됨
# ⚠️ 주의: 프론트엔드 환경변수는 빌드 시 코드에 포함!
# → 민감한 정보는 절대 프론트엔드에 넣지 마세요!
# ✅ 프론트엔드에 넣어도 되는 것:
# - API 엔드포인트 URL
# - 공개 API 키 (클라이언트 사이드 전용)
# - Feature flags
# ❌ 프론트엔드에 넣으면 안 되는 것:
# - 비밀 API 키
# - 데이터베이스 비밀번호
# - JWT 시크릿
# - AWS 시크릿 키
# 💡 해결책: 민감한 작업은 백엔드에서!
Step 4: 배포 환경에서 환경변수 설정
Vercel 배포
# 방법 1: Vercel CLI
vercel env add API_KEY
# 프롬프트:
# What's the value? (입력하면 숨겨짐)
# Add to which environments? Production, Preview, Development
# 방법 2: 웹 대시보드
# 1. Vercel 대시보드 → Project 선택
# 2. Settings → Environment Variables
# 3. 추가:
# Key: DATABASE_URL
# Value: mongodb+srv://user:pass@cluster.mongodb.net/db
# Environments: Production, Preview, Development
# Key: JWT_SECRET
# Value: super-secret-key-xyz-123
# Environments: Production
# 4. Save
# 5. 재배포 (환경변수 적용)
vercel --prod
# 확인
# 배포된 앱에서 정상 작동!
Netlify 배포
# 방법 1: Netlify CLI
netlify env:set API_KEY "abc123xyz789"
netlify env:set DATABASE_URL "mongodb://..."
# 방법 2: 웹 대시보드
# 1. Netlify 대시보드 → Site 선택
# 2. Site settings → Build & deploy
# 3. Environment → Environment variables → Edit variables
# Key | Value
# ----------------- | -------------------------
# REACT_APP_API_URL | https://api.myapp.com
# API_KEY | abc123xyz789
# DATABASE_URL | mongodb+srv://...
# 4. Save
# 5. Trigger deploy (환경변수 적용)
# 확인
netlify open:site
Heroku 배포
# Heroku CLI
heroku login
# 환경변수 설정
heroku config:set NODE_ENV=production
heroku config:set DATABASE_URL="mongodb+srv://..."
heroku config:set JWT_SECRET="my-secret-key"
heroku config:set API_KEY="abc123"
# 확인
heroku config
# 출력:
# DATABASE_URL: mongodb+srv://...
# JWT_SECRET: my-secret-key
# API_KEY: abc123
# NODE_ENV: production
# 환경변수 삭제
heroku config:unset API_KEY
# 배포
git push heroku main
# 앱에서 자동으로 환경변수 로드됨!
Step 5: 보안 모범 사례
# 1. .env.example 파일 생성 (팀원 가이드)
cat > .env.example << 'EOF'
# Server
PORT=3000
NODE_ENV=development
# Database
DATABASE_URL=your_database_url_here
DATABASE_NAME=your_database_name
# API Keys
OPENAI_API_KEY=your_openai_api_key
STRIPE_SECRET_KEY=your_stripe_secret_key
# JWT
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRE=7d
# Instructions:
# 1. Copy this file to .env
# 2. Fill in your actual values
# 3. Never commit .env to Git!
EOF
# 2. .gitignore 확인
cat .gitignore | grep .env
# 출력되어야 할 내용:
# .env
# .env.local
# .env.*.local
# 3. 이미 커밋된 .env 제거 (만약 실수로 커밋했다면)
git rm --cached .env
git commit -m "Remove .env from tracking"
# ⚠️ 주의: 이미 GitHub에 푸시했다면
# → API 키가 노출된 것!
# → 즉시 키를 재발급받으세요!
# 4. GitHub에 비밀 검색 (노출 확인)
# https://github.com/settings/security_analysis
# Secret scanning alerts 확인
// 5. 환경변수 검증 함수
// utils/validateEnv.js
function validateEnv() {
const required = [
'DATABASE_URL',
'JWT_SECRET',
'PORT'
];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
console.error('❌ Missing required environment variables:');
missing.forEach(key => console.error(` - ${key}`));
console.error('\n💡 Please check your .env file');
process.exit(1);
}
// 값 형식 검증
if (process.env.PORT && isNaN(process.env.PORT)) {
console.error('❌ PORT must be a number');
process.exit(1);
}
if (process.env.JWT_SECRET && process.env.JWT_SECRET.length < 32) {
console.warn('⚠️ JWT_SECRET should be at least 32 characters long');
}
console.log('✅ All required environment variables are set');
}
module.exports = validateEnv;
// server.js
require('dotenv').config();
const validateEnv = require('./utils/validateEnv');
// 서버 시작 전 환경변수 검증
validateEnv();
const express = require('express');
// ... 나머지 코드