envscan — .env.example을 손으로 관리하지 말자
.env.example이 실제 코드와 어긋나서 팀원이 배포할 때야 비로소 에러가 터지는 경험은 Node.js 프로젝트라면 거의 공식 통과의례다. 화요일에 DATABASE_URL을 코드에 추가하고, 3주 뒤에 누군가 .env.example을 복사해서 배포하다가 크립틱한 에러로 막힌다. envscan은 이 문제를 도구가 풀 수 있는 문제로 재정의한 접근이다.
정의
envscan은 소스 코드를 AST 레벨로 스캔해서 process.env.* 참조를 찾아내고, 발견된 환경 변수들을 기반으로 .env.example을 자동 생성하는 CLI 도구다.
핵심 아이디어:
process.env.DATABASE_URL이라는 코드 한 줄이 이미 "나는 DATABASE_URL이 필요하다"는 선언이다. 별도의 스키마 파일은 중복이다.
[💡 잠깐! 이 용어는?]
.env.example: 실제 값 없이 "이 프로젝트는 어떤 환경 변수가 필요한지" 목록만 담은 샘플 파일. 팀원이 .env를 만들 때 이 파일을 복사해서 값을 채운다. 문제는 이 파일이 코드와 따로 관리되기 때문에 쉽게 어긋난다는 점이다.
왜 기존 도구로는 부족했나
환경 변수 검증 도구는 이미 여러 개 있다. dotenv-safe(주간 다운로드 15만), envalid(주간 다운로드 48만)가 대표적이다. 둘 다 훌륭하지만, 같은 한계를 공유한다.
| 도구 | 해결하는 문제 | 해결 못 하는 문제 |
|---|---|---|
dotenv-safe | .env.example에 있는데 .env에 없는 변수 검출 | .env.example 자체가 최신인가 |
envalid | 타입 검증, 기본값, 파싱 | 스키마 파일 자체가 최신인가 |
| envscan | 코드에서 실제 참조되는 변수 자동 추출 | — |
기존 도구는 "선언된 스키마"를 기준으로 검증한다. 그런데 그 스키마를 사람이 손으로 관리해야 한다. 결국 .env.example을 손으로 관리하는 문제가 스키마 파일을 손으로 관리하는 문제로 이름만 바뀐 셈이다.
envscan은 다른 쪽에서 접근한다. 코드가 곧 진실의 원천이라는 전제다.
어떻게 동작하나
envscan을 실행하면 프로젝트의 TypeScript/JavaScript 파일을 파싱해 process.env 참조를 수집한다.
$ envscan scan
Found 8 environment variables:
DATABASE_URL type: url src/db.ts:12
PORT type: number src/server.ts:5
JWT_SECRET type: secret src/auth.ts:8, src/auth.ts:23
REDIS_URL type: url src/cache.ts:3
SENDGRID_API_KEY type: secret src/email.ts:7
APP_ENV type: string src/config.ts:2
LOG_LEVEL type: string src/config.ts:3
DEBUG type: boolean src/config.ts:4
Validation: 7/8 vars set. Missing: SENDGRID_API_KEY그리고 이 결과를 바탕으로 .env.example을 생성한다.
# type: url | Found in: src/db.ts:12
DATABASE_URL=
# type: number | Found in: src/server.ts:5
PORT=
# type: secret | Found in: src/auth.ts:8
JWT_SECRET=
# type: url | Found in: src/cache.ts:3
REDIS_URL=
# type: secret | Found in: src/email.ts:7
SENDGRID_API_KEY=
# type: string | Found in: src/config.ts:2
APP_ENV=
# type: string | Found in: src/config.ts:3
LOG_LEVEL=
# type: boolean | Found in: src/config.ts:4
DEBUG=핵심은 각 변수가 어느 파일 어느 라인에서 처음 참조됐는지까지 주석으로 남긴다는 점이다. "이 변수가 왜 필요하지?" 싶을 때 코드 위치를 바로 찾아갈 수 있다.
타입 추론 규칙
envscan은 변수 이름을 기반으로 타입을 추론한다. 이름이 곧 문서 역할을 한다는 아이디어다.
| 이름 패턴 | 추론된 타입 | 예시 |
|---|---|---|
_URL | url | DATABASE_URL, REDIS_URL |
_SECRET, _KEY, _TOKEN | secret | JWT_SECRET, API_KEY |
PORT | number | PORT |
DEBUG | boolean | DEBUG, VERBOSE |
| 그 외 | string | APP_ENV, LOG_LEVEL |
이 분류가 완벽할 필요는 없다. "이 변수는 URL처럼 보인다" 수준의 힌트를 .env.example 주석에 남겨주는 것만으로도 온보딩 비용이 줄어든다.
프리커밋 훅에 붙이기
envscan의 진짜 가치는 정기적으로 돌 때 나온다. 손으로 동기화하지 않고, 도구가 자동으로 맞춘다.
{
"scripts": {
"envscan": "envscan scan",
"envscan:update": "envscan generate --output .env.example"
},
"lint-staged": {
"src/**/*.{ts,js}": ["envscan generate --output .env.example && git add .env.example"]
}
}process.env 참조가 있는 파일이 수정될 때마다 .env.example이 자동으로 업데이트되고 스테이징된다. 사람이 까먹을 수 없는 구조가 된다.
[💡 잠깐! 이 용어는?] lint-staged: git 스테이징된 파일에만 스크립트를 돌리는 도구. 커밋 직전 단계(pre-commit hook)와 함께 자주 쓴다. "수정된 파일에만" 포맷팅이나 체크를 돌릴 수 있어서 CI 시간을 아낀다.
검증(validate) 모드
스캔만 하는 게 아니라, 현재 환경과 대조하는 검증 모드도 있다.
$ envscan validate
Checking required environment variables...
✓ DATABASE_URL (postgres://...)
✓ PORT (3000)
✓ JWT_SECRET (********)
✗ SENDGRID_API_KEY (not set)
✓ REDIS_URL (redis://...)
...
Error: 1 required variable is missing: SENDGRID_API_KEY
Exit code: 1앱을 실행하기 전에 envscan validate를 돌리면, 런타임 몇 초 뒤에 크립틱한 에러로 죽는 대신 시작 시점에 명확한 메시지로 실패한다. 개발자가 문제를 발견하는 시점을 30분에서 30초로 줄이는 변화다.
엣지 케이스 처리
process.env를 쓰는 방식이 다양해서, 정적 분석으로 잡히지 않는 케이스가 있다. envscan은 이런 경우를 명시적으로 다룬다.
| 케이스 | 처리 방식 |
|---|---|
process.env.DATABASE_URL | 정적 이름, 바로 수집 |
process.env[key] (동적 키) | 경고로 표시, 무시하지 않음 |
템플릿 리터럴 process.env['DB_' + x] | 경고로 표시 |
| 테스트 파일의 참조 | 별도 태그로 분리 가능 |
동적 키를 조용히 스킵하지 않고 경고로 띄워주는 것이 특히 중요하다. 사용자가 "아, 이건 수동으로 확인해야겠네" 하고 바로 알아챌 수 있다.
제한 사항
몇 가지 알아둘 점이 있다.
- 아직 베타 단계다. 안정화를 기다리는 팀은 대기자 명단에 등록해서 소식을 받는 게 낫다
- CLI는 무료지만 GitHub Action 통합(PR 코멘트로 누락된 변수 지적)은 월 $6 유료 플랜
- 모노레포나 가상 파일 시스템 기반 툴링과의 호환은 개별 검증이 필요하다
.env자체를 관리하는 것이 아니라.env.example을 자동화하는 것이 목적이다. 실제 값은 여전히 사람이 관리한다
마무리
.env.example을 손으로 관리하는 것은 "매번 양쪽을 동기화해야 하는데 한쪽은 까먹기 쉬운 구조" 그 자체다. envscan은 이 구조를 "한쪽(코드)만 관리하고 나머지는 생성물"로 바꾼다.
- 코드의
process.env참조를 AST로 스캔해 실제 필요한 변수 목록을 뽑는다 - 이름 기반 타입 추론으로
.env.example에 힌트를 자동 주석 처리한다 - 프리커밋 훅에 붙여두면 사람이 까먹을 여지가 사라진다
- 검증 모드로 앱 시작 시점에 명확한 에러 메시지를 얻는다
직접 써보기 전에 대기자 명단에 이름을 올려두는 것도 방법이다.
참고:
- Why I Stopped Maintaining .env.example by Hand: https://dev.to/ckmtools/why-i-stopped-maintaining-envexample-by-hand-473j
- dotenv-safe: https://github.com/rolodato/dotenv-safe
- envalid: https://github.com/af/envalid
관심 있을 만한 포스트
SVG 아이콘 — 코드 배포 없이 프로덕트 팀이 직접 관리하는 법
CSS mask-image와 S3를 조합해 개발자 개입 없이 아이콘을 교체하는 패턴을 소개한다.
Naver FE News 2026년 4월 — 49MB 웹 페이지부터 Temporal Stage 4까지
Naver FE News 2026년 4월호에서 프론트엔드 개발자가 주목할 6가지 소식을 선별해 정리한다.
node:vfs — Node.js에 가상 파일 시스템이 필요한 이유
Matteo Collina가 제안한 node:vfs 모듈이 해결하려는 4가지 문제와 아키텍처를 분석한다.
Bun이 빠른 건 맞다 — 그런데 당신의 이벤트 루프가 문제다
Bun으로 바꿔도 p99가 개선되지 않는 이유. 런타임 선택보다 먼저 봐야 할 진짜 병목 지점들.
Bun vs Node.js vs Deno — 뭐가 다른지, 그래서 뭘 쓰면 좋은지 (2026 기준)
런타임 3대장 비교: 호환성(Node), 속도/번들(Bun), 올인원/보안(Deno). 팀/프로덕트 상황별 선택 기준과 체크리스트까지 정리.
Action-Reducer-State의 귀환 — 프론트엔드 패턴이 서버를 점령한 이유
프론트엔드에서 익숙한 Redux의 Action-Reducer-State 패턴을 서버 사이드에 적용한 당근마켓의 이벤트 소싱 라이브러리 Ventyd를 분석한다.
VS Code 1.116 — 에이전트 디버깅, 포그라운드 터미널, 내장 Copilot
2026년 4월 VS Code 1.116이 에이전트 경험, 터미널, Chat UX, 내장 브라우저를 개선한 핵심 변경사항을 정리한다.
VS Code 에이전트 — 실전 개발에서 쓸 수 있게 만드는 세 가지 축
VS Code 1.110이 도입한 컨텍스트 관리, 에이전트 제어, 확장성 기능이 AI 에이전트를 실무에 투입 가능하게 만든 방식을 분석한다.