Varlock으로 환경 변수를 안전하게 관리하기
프로젝트를 진행하다 보면 .env 파일에 API 키, 데이터베이스 비밀번호 같은 민감한 정보를 저장하게 됩니다. dotenv를 사용해서 이 값들을 불러오는 건 이제 거의 표준처럼 자리 잡았는데요. 그런데 이런 경험 한 번쯤 있지 않으신가요?
새로운 팀원이 프로젝트를 클론받고 나서 .env.example을 보고 .env를 만들었는데, 필수 환경 변수 하나를 빼먹어서 런타임에 에러가 나는 상황. 아니면 AI 코딩 도우미에게 코드를 맡겼더니 .env 파일의 실제 비밀값까지 컨텍스트에 포함돼 버리는 아찔한 순간. 😅
Varlock은 바로 이런 문제들을 해결하기 위해 만들어진 도구입니다. dotenv를 대체하면서 스키마 기반 검증, AI 안전성, 시크릿 유출 방지까지 환경 변수 관리에 필요한 거의 모든 기능을 제공하는데요. 이 글에서는 Varlock의 핵심 기능을 살펴보고 실제 프로젝트에 적용하는 방법을 알아보겠습니다.
dotenv의 한계
우선 왜 dotenv만으로는 부족한지 짚어볼게요.
dotenv는 .env 파일에서 환경 변수를 읽어 process.env에 주입하는 단순한 역할을 합니다. 잘 동작하지만, 프로젝트가 커지면 몇 가지 불편한 점이 생깁니다.
우선 .env.example 파일을 따로 관리해야 하는데, 이 파일은 실제 .env와 동기화가 안 되는 경우가 많습니다. 새 환경 변수를 추가하고 .env.example 업데이트를 깜빡하면 팀원들이 고생하게 되죠. 또한 환경 변수의 타입이나 형식을 검증하는 기능이 없어서 포트 번호에 문자열이 들어가도, API 키가 잘못된 형식이어도 런타임에서야 문제를 발견합니다.
보안 측면에서도 한계가 있습니다. .gitignore에 .env를 추가하는 건 기본이지만, 커밋하기 전에 코드에 실수로 하드코딩된 비밀값을 검출하거나, 런타임에 로그로 민감한 값이 출력되는 걸 막는 건 dotenv의 영역이 아닙니다.
그리고 최근에는 AI 에이전트가 개발 과정에 깊숙이 들어오면서 새로운 고민이 생겼습니다. AI에게 프로젝트 컨텍스트를 제공할 때 .env 파일의 구조는 알려주고 싶지만 실제 비밀값은 노출하고 싶지 않거든요. dotenv에는 이런 구분 자체가 없습니다.
Varlock 시작하기
Varlock은 여러 가지 방법으로 설치할 수 있습니다. Node.js 프로젝트라면 npx나 bunx로 초기화 마법사를 실행하는 게 가장 간편합니다.
bunx varlock init
# 또는
npx varlock init
Homebrew를 사용한다면 독립 바이너리로도 설치 가능합니다.
brew install dmno-dev/tap/varlock
Linux 서버나 CI 환경에서는 설치 스크립트를 사용할 수 있고, Docker 이미지도 제공됩니다.
curl -sSfL https://varlock.dev/install.sh | sh -s
varlock init을 실행하면 기존 .env 파일을 분석해서 .env.schema 파일을 자동으로 생성해 줍니다. 이 .env.schema가 Varlock의 핵심인데요, 기존에 .env.example이 하던 역할을 대체하면서 훨씬 강력한 기능을 제공합니다.
.env.schema 파일
.env.schema 파일은 환경 변수의 구조와 규칙을 정의하는 파일입니다. 일반 .env 파일처럼 생겼지만, @env-spec 데코레이터 주석을 사용해서 각 변수의 타입, 필수 여부, 민감 여부 같은 메타데이터를 지정할 수 있습니다.
간단한 예제를 볼까요?
# @defaultSensitive=false @defaultRequired=infer @currentEnv=$APP_ENV
# ---
# 애플리케이션 환경
# @type=enum(development, preview, production, test)
APP_ENV=development
# API 서버 포트
# @type=port
API_PORT=8080
# API 서버 URL (포트를 참조)
# @type=url
API_URL=http://localhost:${API_PORT}
# OpenAI API 키
# @required @sensitive @type=string(startsWith=sk-)
OPENAI_API_KEY=
첫 줄의 # --- 위에 있는 주석은 파일 전체에 적용되는 전역 설정입니다. @defaultSensitive=false는 기본적으로 변수를 비민감으로 취급하고, @defaultRequired=infer는 기본값이 있으면 선택, 없으면 필수로 자동 판단합니다.
각 변수 위에 붙은 @type 데코레이터가 핵심인데요. port로 지정하면 유효한 포트 번호인지, url로 지정하면 올바른 URL 형식인지, enum으로 지정하면 허용된 값 중 하나인지 자동으로 검증합니다. OPENAI_API_KEY에는 @sensitive 데코레이터가 붙어 있어서 이 값은 로그에 출력되지 않고, AI 에이전트에게도 노출되지 않습니다.
API_URL처럼 ${API_PORT}를 참조해서 다른 변수의 값을 조합하는 것도 가능합니다. dotenv에서는 별도 플러그인이 필요했던 기능이죠.
환경 변수 로딩
Varlock에서 환경 변수를 로딩하는 방법은 프로젝트의 성격에 따라 다릅니다.
TypeScript나 JavaScript 프로젝트에서는 코드 최상단에서 varlock/auto-load를 임포트하면 됩니다. dotenv의 config() 호출과 비슷한데, 차이점은 .env.schema에 정의된 규칙에 따라 자동으로 검증까지 수행한다는 점입니다.
import "varlock/auto-load";
// process.env에서 검증된 환경 변수 사용
console.log(process.env.API_PORT);
console.log(process.env.API_URL);
타입 안전성까지 원한다면 varlock/env에서 ENV 객체를 임포트할 수 있습니다. .env.schema를 기반으로 자동 생성된 타입 정의 덕분에 에디터에서 자동 완성이 되고, 존재하지 않는 환경 변수에 접근하면 타입 에러가 발생합니다.
import "varlock/auto-load";
import { ENV } from "varlock/env";
// ENV 객체는 타입이 지정되어 있어서 자동 완성이 됨
const apiUrl = ENV.API_URL; // string 타입으로 추론
const port = ENV.API_PORT; // number 타입으로 추론
Python, Ruby, Go 같은 다른 언어로 작성된 프로젝트에서는 CLI의 run 명령어를 사용합니다.
varlock run -- python my_script.py
varlock run -- ruby app.rb
varlock run은 .env.schema의 규칙에 따라 환경 변수를 검증하고 로딩한 다음, 자식 프로세스에 주입해서 실행합니다. 언어에 상관없이 동일한 검증 로직을 적용할 수 있는 거죠.
AI 안전성
Varlock이 최근 많은 관심을 받는 이유 중 하나가 바로 AI 안전성입니다. Cursor, Copilot, Claude Code 같은 AI 코딩 도우미가 보편화되면서, 프로젝트의 파일을 AI에게 보여줄 때 비밀값이 노출되는 문제가 현실적인 위험이 됐습니다.
.env.schema 파일은 Git에 커밋해도 안전합니다. 실제 비밀값 없이 변수의 구조, 타입, 설명만 담고 있으니까요. AI 에이전트는 이 스키마 파일을 읽어서 프로젝트가 어떤 환경 변수를 사용하는지 파악하면서도, 실제 API 키나 비밀번호에는 접근하지 못합니다.
기존에는 .env.example이 비슷한 역할을 했지만, .env.example은 단순한 텍스트 파일이라 실제 .env와 쉽게 동기화가 깨집니다. .env.schema는 Varlock이 직접 해석하는 파일이기 때문에 항상 실제 설정과 일치하는 진실의 단일 소스(single source of truth) 역할을 합니다.
시크릿 유출 방지
Varlock은 비밀값이 유출되는 것을 여러 단계에서 막아줍니다.
먼저 varlock scan 명령어로 코드베이스 전체를 스캔하여 하드코딩된 비밀값을 찾을 수 있습니다.
varlock scan
이 명령어는 .env.schema에서 @sensitive로 표시된 변수의 값이 소스 코드 어딘가에 직접 들어가 있지는 않은지 검사합니다. Git pre-commit 훅과 연동하면 비밀값이 포함된 코드가 커밋되는 것을 원천적으로 차단할 수 있습니다.
런타임에서도 보호가 동작합니다. @sensitive로 표시된 환경 변수의 값은 console.log나 로깅 라이브러리의 출력에서 자동으로 마스킹됩니다. 개발자가 디버깅 중에 실수로 비밀값을 로그에 남기는 흔한 실수를 방지해 주는 거죠.
멀티 환경 관리
실제 프로젝트에서는 개발, 스테이징, 프로덕션 환경에 따라 다른 값을 사용해야 하는 경우가 많습니다. Varlock은 .env.schema의 @currentEnv 설정을 기반으로 환경별 .env 파일을 자동으로 로딩합니다.
# @currentEnv=$APP_ENV
# ---
# @type=enum(development, staging, production)
APP_ENV=development
# @type=url
DATABASE_URL=postgres://localhost:5432/myapp
APP_ENV가 development이면 .env.development를, production이면 .env.production을 자동으로 찾아서 로딩합니다. 값의 우선순위는 다음과 같습니다:
- 프로세스 환경 변수 (최우선)
.env.local(로컬 오버라이드).env.{환경}(환경별 파일).env(기본 파일).env.schema의 기본값 (최하위)
dotenv에서는 이런 환경별 로딩을 직접 구현해야 했는데, Varlock은 이 패턴을 기본으로 제공합니다.
시크릿 관리자 연동
팀 규모가 커지면 .env 파일을 직접 공유하는 대신 시크릿 관리자를 사용하게 됩니다. Varlock은 주요 시크릿 관리 서비스와의 연동을 플러그인으로 제공합니다.
@varlock/1password-plugin— 1Password@varlock/aws-secrets-plugin— AWS Secrets Manager@varlock/azure-keyvault-plugin— Azure Key Vault@varlock/gcp-secret-manager-plugin— Google Secret Manager@varlock/infisical-plugin— Infisical@varlock/bitwarden-plugin— Bitwarden
예를 들어 1Password를 연동하면 .env.schema에서 직접 시크릿을 참조할 수 있습니다.
# @plugin(@varlock/1password-plugin)
# @initOp(token=$OP_TOKEN, allowAppAuth=forEnv(dev))
# ---
# 1Password 서비스 계정 토큰
# @type=opServiceAccountToken @sensitive
OP_TOKEN=
# 1Password에서 데이터베이스 비밀번호 가져오기
DB_PASS=op(op://my-vault/database-password/password)
# 1Password에서 API 키 가져오기
API_KEY=op(op://api-vault/stripe/api-key)
op(...) 구문으로 1Password의 시크릿 참조(secret reference)를 직접 사용할 수 있습니다. 개발자가 로컬에서는 1Password 앱 인증을 사용하고, CI/CD에서는 서비스 계정 토큰을 사용하는 식으로 환경에 따라 인증 방식을 달리할 수도 있습니다.
프레임워크 통합
Varlock은 인기 있는 프레임워크들과의 통합도 제공합니다. Next.js, Vite, Astro 같은 프레임워크에서는 환경 변수를 다루는 고유한 방식이 있는데요. 예를 들어 Next.js의 NEXT_PUBLIC_ 접두사나 Vite의 VITE_ 접두사 같은 규칙이죠. Varlock의 프레임워크 통합 플러그인은 이런 규칙을 이해하고, 클라이언트 측에 노출되면 안 되는 민감한 환경 변수가 실수로 번들에 포함되는 것을 감지합니다.
# Next.js 프로젝트에 Varlock 통합 추가
bunx varlock init --framework next
CLI 활용
Varlock의 CLI는 환경 변수를 검증하고 관리하는 데 유용한 명령어들을 제공합니다.
현재 설정된 환경 변수를 .env.schema의 규칙에 따라 검증하고 확인하려면 load 명령어를 사용합니다.
varlock load
필수 변수가 빠져 있거나 타입이 맞지 않으면 어떤 변수에 문제가 있는지 명확한 에러 메시지를 보여줍니다. CI 파이프라인에서 배포 전에 환경 변수를 검증하는 용도로도 활용할 수 있습니다.
앞서 언급한 유출 스캔은 이렇게 실행합니다.
varlock scan
이 명령어는 프로젝트의 소스 코드를 분석하여 .env.schema에서 @sensitive로 표시된 값이 하드코딩되어 있는지 검사합니다. Git 훅과 연동하면 커밋 전에 자동으로 실행되도록 설정할 수 있습니다.
VSCode 확장
Varlock은 VSCode 확장도 제공합니다. .env.schema 파일을 편집할 때 @env-spec 데코레이터의 자동 완성, 구문 강조, 인라인 검증 같은 기능을 사용할 수 있습니다. 에디터에서 바로 스키마 오류를 확인할 수 있어서 개발 경험이 한층 좋아집니다.
dotenv에서 마이그레이션
이미 dotenv를 사용하고 있는 프로젝트에서 Varlock으로 전환하는 건 꽤 수월합니다. varlock init이 기존 .env 파일을 분석해서 .env.schema를 생성해 주기 때문입니다.
기본적인 마이그레이션 순서는 다음과 같습니다.
# 1. Varlock 설치 및 초기화
bunx varlock init
# 2. 생성된 .env.schema 검토 및 타입 데코레이터 추가
# 3. 코드에서 dotenv 임포트를 varlock으로 교체
기존에 dotenv를 사용하던 코드는 이렇게 바꾸면 됩니다.
// 변경 전
import dotenv from "dotenv";
dotenv.config();
console.log(process.env.API_KEY);
// 변경 후
import "varlock/auto-load";
import { ENV } from "varlock/env";
console.log(ENV.API_KEY); // 타입 안전!
process.env를 직접 사용하는 것도 여전히 가능하지만, ENV 객체를 사용하면 타입 검사와 자동 완성의 혜택을 받을 수 있습니다.
마치며
Varlock은 환경 변수 관리의 여러 고민을 한 번에 해결해 주는 도구입니다. .env.schema라는 하나의 파일로 검증, 타입 안전성, AI 안전성, 시크릿 관리까지 통합적으로 다루는 접근이 인상적입니다.
특히 AI 코딩 도구를 적극 활용하는 팀이라면 .env.schema를 통해 프로젝트 설정의 구조를 AI에게 안전하게 공유할 수 있다는 점이 매력적일 겁니다. 시크릿 유출 스캔이나 런타임 보호 같은 보안 기능도 .gitignore에 .env를 추가하는 것에서 한 단계 더 나아간 방어 수단을 제공합니다.
dotenv 기반의 환경 변수 관리에 불편함을 느끼고 있었다면, Varlock을 한번 시도해 보시길 권합니다. GitHub 저장소에서 소스 코드도 살펴보실 수 있습니다.
This work is licensed under
CC BY 4.0