Skip to main content

🔐 환경변수 관리

📖 정의

**환경변수(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');
// ... 나머지 코드

Step 6: 비밀 관리 서비스 (고급)

# AWS Secrets Manager 사용 예시

# 1. AWS CLI 설치 및 설정
aws configure

# 2. 비밀 생성
aws secretsmanager create-secret \
--name myapp/prod/database \
--secret-string '{"username":"admin","password":"secret123"}'

# 3. Node.js에서 사용
npm install @aws-sdk/client-secrets-manager
// config/secrets.js
const {
SecretsManagerClient,
GetSecretValueCommand
} = require('@aws-sdk/client-secrets-manager');

const client = new SecretsManagerClient({
region: process.env.AWS_REGION || 'us-east-1'
});

async function getSecret(secretName) {
try {
const response = await client.send(
new GetSecretValueCommand({ SecretId: secretName })
);

return JSON.parse(response.SecretString);
} catch (error) {
console.error('Error retrieving secret:', error);
throw error;
}
}

// 사용
async function connectDatabase() {
const dbSecrets = await getSecret('myapp/prod/database');

const connectionString = `mongodb://${dbSecrets.username}:${dbSecrets.password}@...`;

// DB 연결
}

module.exports = { getSecret };
# 다른 비밀 관리 서비스:

# 1. HashiCorp Vault
# - 엔터프라이즈급 비밀 관리
# - 동적 비밀 생성
# - 접근 제어

# 2. Azure Key Vault
# - Microsoft Azure 통합
# - 키, 비밀, 인증서 관리

# 3. Google Cloud Secret Manager
# - GCP 통합
# - 자동 순환

# 4. Doppler
# - 개발자 친화적
# - 팀 협업 기능
# - GUI 제공

Step 7: 환경변수 디버깅

// debug.js
// 환경변수 확인 스크립트

require('dotenv').config();

console.log('🔍 Environment Variables Debug\n');

// 모든 환경변수 출력 (민감한 정보 마스킹)
function maskSecret(value) {
if (!value || value.length < 8) return '***';
return value.substring(0, 4) + '***' + value.substring(value.length - 4);
}

const envVars = [
'NODE_ENV',
'PORT',
'DATABASE_URL',
'JWT_SECRET',
'API_KEY',
'STRIPE_SECRET_KEY'
];

console.log('📋 Current Environment Variables:\n');

envVars.forEach(key => {
const value = process.env[key];
if (value) {
// 민감한 키워드 감지
const isSensitive = /secret|key|password|token/i.test(key);
const displayValue = isSensitive ? maskSecret(value) : value;

console.log(`${key}: ${displayValue}`);
} else {
console.log(`${key}: NOT SET`);
}
});

console.log('\n💡 Tip: Check your .env file if any required variables are missing');
# 실행
node debug.js

# 출력:
# 🔍 Environment Variables Debug
#
# 📋 Current Environment Variables:
#
# ✅ NODE_ENV: development
# ✅ PORT: 3000
# ✅ DATABASE_URL: mong***...7017
# ✅ JWT_SECRET: my-s***-key
# ✅ API_KEY: abc1***xyz9
# ❌ STRIPE_SECRET_KEY: NOT SET
#
# 💡 Tip: Check your .env file if any required variables are missing

🤔 자주 묻는 질문

Q1. .env 파일을 실수로 GitHub에 올렸어요!

A:

# 🚨 긴급 대응 가이드

# 1. 즉시 Git에서 제거
git rm --cached .env
git commit -m "Remove .env from version control"
git push origin main

# 2. .gitignore에 추가 (이미 있는지 확인)
echo ".env" >> .gitignore
git add .gitignore
git commit -m "Add .env to gitignore"
git push origin main

# 3. ⚠️ 중요: 모든 비밀 키 즉시 재발급!
# - API 키 재생성
# - 데이터베이스 비밀번호 변경
# - JWT 시크릿 변경

# 왜? Git 히스토리에 남아있음!
# 삭제해도 이전 커밋에서 볼 수 있음

# 4. Git 히스토리에서 완전 제거 (고급)
# BFG Repo-Cleaner 사용
git clone https://github.com/username/repo.git --mirror
java -jar bfg.jar --delete-files .env repo.git
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force

# ⚠️ 주의: Force push는 팀원들에게 영향!
# 팀원들에게 알리고 재클론 요청

# 5. GitHub Secret Scanning 확인
# https://github.com/settings/security_analysis
# 노출된 키가 감지되면 알림 받음

# 6. 보안 점검
# - 최근 API 사용 로그 확인
# - 비정상적인 활동 모니터링
# - 2FA 활성화

# 예방책:
# ✅ .gitignore에 .env 먼저 추가
# ✅ pre-commit hook 설정
# ✅ git-secrets 도구 사용

Q2. 팀원들과 환경변수를 안전하게 공유하려면?

A:

# 방법 1: .env.example 파일 (권장)

# .env.example (Git에 커밋)
cat > .env.example << 'EOF'
# Database
DATABASE_URL=mongodb://localhost:27017/myapp
DATABASE_NAME=myapp_dev

# API Keys (get from team lead)
OPENAI_API_KEY=
STRIPE_SECRET_KEY=

# JWT
JWT_SECRET=generate_random_32_char_string

# 🔒 Get actual values from:
# - Team password manager (1Password, LastPass)
# - Team documentation
# - Ask team lead
EOF

# 팀원 온보딩:
# 1. 저장소 클론
# 2. .env.example을 .env로 복사
# 3. 실제 값 입력
cp .env.example .env

# 방법 2: 비밀번호 관리자 (1Password, LastPass)
# - 팀 Vault 생성
# - 환경변수를 Secure Note로 저장
# - 팀원들에게 접근 권한 부여

# 방법 3: Doppler (추천!)
# https://doppler.com

# 1. Doppler에 환경변수 저장
# 2. 팀원 초대
# 3. CLI로 로컬에서 사용

doppler login
doppler setup
doppler run -- npm start

# 장점:
# ✅ 중앙 집중식 관리
# ✅ 버전 히스토리
# ✅ 접근 제어
# ✅ 자동 동기화

# 방법 4: 암호화된 파일 (간단한 프로젝트)
# .env 파일을 암호화하여 공유

# 암호화
gpg -c .env
# .env.gpg 파일 생성

# Git에 .env.gpg 커밋 (암호화된 상태)

# 팀원이 복호화
gpg -d .env.gpg > .env
# 비밀번호 입력 (별도 공유)

# ❌ 피해야 할 방법:
# - Slack/Discord에 복붙
# - 이메일로 전송
# - 스크린샷 공유

Q3. 프론트엔드와 백엔드 환경변수 차이는?

A:

// 백엔드 (Node.js/Express)
// - 서버에서만 실행
// - 환경변수가 노출되지 않음

// .env
DATABASE_URL=mongodb://user:pass@server/db
JWT_SECRET=super-secret-key-12345
STRIPE_SECRET_KEY=sk_live_abc123

// 사용
const dbUrl = process.env.DATABASE_URL; // 안전!
// 클라이언트가 절대 볼 수 없음

// -----------------------------------------

// 프론트엔드 (React/Vue/Angular)
// - 브라우저에서 실행
// - 빌드 시 코드에 포함됨
// - 누구나 볼 수 있음!

// .env
REACT_APP_API_URL=https://api.myapp.com
REACT_APP_GOOGLE_MAPS_KEY=AIzaSyABC123 // Public key

// 빌드 후 코드:
const apiUrl = "https://api.myapp.com"; // 하드코딩됨!

// 브라우저 개발자 도구에서 볼 수 있음:
// 1. Sources 탭
// 2. main.js 파일 열기
// 3. 검색: api.myapp.com
// → 찾을 수 있음!

// 규칙:
// ✅ 프론트엔드에 넣어도 되는 것:
// - API 엔드포인트 URL
// - 공개 API 키 (클라이언트용)
// - Feature flags
// - 공개 설정값

// ❌ 프론트엔드에 넣으면 안 되는 것:
// - 비밀 API 키
// - 데이터베이스 URL
// - JWT 시크릿
// - 서버 비밀번호

// 해결책: 백엔드 프록시 사용
// 프론트엔드:
fetch('/api/data'); // 비밀 키 없음

// 백엔드:
app.get('/api/data', (req, res) => {
const apiKey = process.env.SECRET_API_KEY; // 서버에만 있음
// 외부 API 호출
const data = await callExternalAPI(apiKey);
res.json(data);
});

// 프론트엔드는 백엔드를 통해서만 접근!

Q4. 환경변수가 undefined로 나와요!

A:

// 문제 해결 체크리스트

// 1. dotenv가 최상단에 있는지 확인
// ❌ 잘못된 순서
const express = require('express');
require('dotenv').config(); // 너무 늦음!
const apiKey = process.env.API_KEY; // undefined

// ✅ 올바른 순서
require('dotenv').config(); // 맨 위!
const express = require('express');
const apiKey = process.env.API_KEY; // OK

// 2. .env 파일 위치 확인
// .env 파일은 프로젝트 루트에 있어야 함
project/
├─ .env ← 여기!
├─ server.js
├─ package.json
└─ src/
└─ routes/

// 다른 위치에 있다면:
require('dotenv').config({ path: './config/.env' });

// 3. React: REACT_APP_ 접두사 확인
// ❌ 잘못됨
API_URL=http://localhost:3000

// ✅ 올바름
REACT_APP_API_URL=http://localhost:3000

// 4. 환경변수 이름 확인 (오타)
// .env
API_KEY=abc123

// 코드
const key = process.env.API_KEX; // 오타! → undefined
const key = process.env.API_KEY; // OK

// 5. 따옴표 확인
// ❌ 잘못됨 (따옴표 포함됨)
API_KEY="abc123"

// 코드에서:
process.env.API_KEY // "abc123" (따옴표 포함!)

// ✅ 올바름
API_KEY=abc123

// 6. 공백 확인
// ❌ 잘못됨
API_KEY = abc123 // 공백 있음

// ✅ 올바름
API_KEY=abc123

// 7. 주석 확인
# 이것은 주석입니다
API_KEY=abc123 # 올바른 주석

// 8. 서버 재시작 확인
// .env 파일 수정 후:
// - 개발 서버 재시작 필요
// - nodemon은 자동 재시작 안 함 (.env 변경 시)

// nodemon.json 설정
{
"watch": [".env", "src/"]
}

// 9. 디버깅 코드 추가
console.log('API_KEY:', process.env.API_KEY);
console.log('All env vars:', Object.keys(process.env));

// 10. .env 파일 인코딩 확인
// UTF-8이어야 함
// UTF-8 BOM이면 문제 발생 가능

Q5. 환경변수 vs Config 파일의 차이는?

A:

// 환경변수 (.env)
// - 민감한 정보 (API 키, 비밀번호)
// - 환경별로 다른 값
// - 외부에서 주입

// .env
DATABASE_URL=mongodb://localhost:27017/db
JWT_SECRET=super-secret-key
API_KEY=abc123

// 사용
const dbUrl = process.env.DATABASE_URL;

// 장점:
// ✅ 코드와 분리
// ✅ 환경별 설정 쉬움
// ✅ 보안 (Git에 커밋 안 함)
// ✅ 배포 플랫폼 지원

// 단점:
// ❌ 타입 없음 (모두 문자열)
// ❌ 검증 어려움
// ❌ 중첩 구조 불가

// -----------------------------------------

// Config 파일 (config.js)
// - 애플리케이션 설정
// - 비민감 정보
// - 코드로 관리

// config/app.js
module.exports = {
app: {
name: 'My App',
version: '1.0.0',
defaultLanguage: 'ko'
},
pagination: {
defaultPageSize: 20,
maxPageSize: 100
},
upload: {
maxFileSize: 5 * 1024 * 1024, // 5MB
allowedTypes: ['.jpg', '.png', '.pdf']
},
features: {
enableComments: true,
enableNotifications: true
}
};

// 사용
const config = require('./config/app');
const pageSize = config.pagination.defaultPageSize;

// 장점:
// ✅ 타입 안전
// ✅ 중첩 구조
// ✅ 계산된 값
// ✅ Git 커밋 가능 (버전 관리)

// 단점:
// ❌ 민감 정보 불가
// ❌ 환경별 관리 복잡

// -----------------------------------------

// 베스트 프랙티스: 둘 다 사용!

// .env (민감한 정보)
DATABASE_URL=mongodb://...
JWT_SECRET=secret-key

// config.js (앱 설정 + 환경변수 통합)
require('dotenv').config();

module.exports = {
// 환경변수에서 가져오기
database: {
url: process.env.DATABASE_URL,
poolSize: 10
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: '7d'
},

// 정적 설정
app: {
name: 'My App',
version: '1.0.0'
},
pagination: {
defaultPageSize: 20
}
};

// 사용
const config = require('./config');
const dbUrl = config.database.url; // 깔끔!
const pageSize = config.pagination.defaultPageSize;

🎓 다음 단계

환경변수 관리를 마스터했다면, 다음을 학습해보세요:

  1. 첫 웹사이트 배포하기 - Vercel/Netlify에서 환경변수 설정하기
  2. REST API 서버 만들기 - API 키로 보안 강화하기
  3. 에러 로깅과 모니터링 - Sentry 환경변수 설정하기

추가 학습 자료

# 환경변수 보안 도구

# 1. git-secrets
# AWS 키 등이 커밋되는 것을 방지
brew install git-secrets
git secrets --install
git secrets --register-aws

# 2. truffleHog
# Git 히스토리에서 비밀 탐지
pip install truffleHog
truffleHog --regex --entropy=True https://github.com/user/repo

# 3. detect-secrets
# 코드에서 비밀 감지
pip install detect-secrets
detect-secrets scan > .secrets.baseline

# 4. pre-commit hook
# .git/hooks/pre-commit
#!/bin/sh
if git diff --cached --name-only | grep -q "\.env$"; then
echo "❌ Error: Attempting to commit .env file!"
exit 1
fi

🎬 마무리

환경변수 관리의 핵심:

  • .env 파일: 로컬 개발 환경 설정
  • .gitignore: 절대 GitHub에 올리지 않기
  • 환경별 분리: dev, staging, production
  • 보안: 민감한 정보는 환경변수로만
  • 검증: 필수 환경변수 확인
  • 팀 공유: .env.example과 비밀번호 관리자

환경변수를 올바르게 관리하면 보안을 유지하면서도 유연한 설정이 가능합니다. API 키가 노출되지 않도록 항상 주의하고, 팀원들과 안전하게 공유하세요! 🔐✨

.env 파일을 Git에 커밋하기 전에 항상 두 번 확인하세요!