dotenv로 환경 변수를 .env 파일로 관리하기
많은 Node.js 프로젝트에서 환경 변수를 좀 더 효과적으로 관리하기 위해서 dotenv
라는 라이브러리를 사용하고 있습니다.
이번 포스팅에서는 환경 변수를 파일에 저장해놓고 접근할 수 있게 도와주는 dotenv
라이브러리에 대해서 알아보겟습니다.
dotenv 패키지 설치
npm 패키지 매니저를 이용하여 dotenv
라이브러리를 Node.js 프로젝트에 설치합니다.
$ npm i dotenv
.env 파일 작성
dotenv
라이브러리는 아무 설정을 하지 않으면 현재 디렉토리에 위치한 .env
파일로 부터 환경 변수를 읽어오는데요.
.env
파일을 생성하고, 그 안에 필요한 환경 변수를 키=값
의 포맷으로 나열해보겠습니다.
DB_HOST=localhost
DB_USER=root
DB_PASS=1234
이렇게 .env
파일에 저장해놓은 환경 변수들을 dotenv
라이브러리를 이용해서 process.env
에 설정할 수 있는데요.
process.env
가 생소하신 분들은 관련 포스팅를 통해서 Node.js에서 환경 변수 다루는 방법을 먼저 학습하시기를 추천드립니다.
본인 프로젝트가 CommonJS 기반인지 ES 모듈 기반인지에 따라 라이브러리 사용법이 약간 상이하므로 나눠서 설명드리도록 하겠습니다.
CommonJS에서 환경 변수 불러오기 (require)
먼저 Node.js에서 전통적으로 제공해왔던 모듈 시스템인 CommonJS에서 dotenv
라이브러리를 어떻게 사용하는지 알아볼께요.
프로그램을 구동할 때 제일 먼저 실행되는 자바스크립트 파일(ex. index.js
, main.js
)의 최상위에 다음과 같이 dotenv
라이브러를 임포트한 후 config()
함수를 호출해주기만 하면 됩니다.
require("dotenv").config();
console.log("DB_HOST:", process.env.DB_HOST);
console.log("DB_USER:", process.env.DB_USER);
console.log("DB_PASS:", process.env.DB_PASS);
예를 들어, 위 코드를 실행하면 process.env
로 부터 읽어진 확경 변수가 출력되는 것을 볼 수 있습니다.
$ node index.js
DB_HOST: localhost
DB_USER: root
DB_PASS: 1234
하지만, 같은 파일 내에서 dotenv
라이브러리의 config()
함수를 호출하기 전에 process.env
를 읽으면 안 되니 주의하셔야 합니다.
console.log("DB_HOST:", process.env.DB_HOST);
console.log("DB_USER:", process.env.DB_USER);
console.log("DB_PASS:", process.env.DB_PASS);
require("dotenv").config();
$ node index.js
DB_HOST: undefined
DB_USER: undefined
DB_PASS: undefined
ES 모듈에서 환경 변수 불러오기 (import)
ES 모듈을 사용하고 있는 Node.js 환경에서는 require
대신에 import
키워드를 사용해서 dotenv
패키지를 불러오면 됩니다.
import dotenv from "dotenv";
dotenv.config();
console.log("DB_HOST", process.env.DB_HOST);
console.log("DB_USER:", process.env.DB_USER);
console.log("DB_PASS:", process.env.DB_PASS);
$ node index.mjs
DB_HOST: localhost
DB_USER: root
DB_PASS: 1234
Node.js에서 ES 모듈(import/export) 사용하는 방법은 관련 포스팅을 참고 바랍니다.
다른 파일에 환경 변수 저장하기
만약에 .env
가 아닌 다른 경로에 있는 파일에 환경 변수를 저장해야 한다면 어떻게 해야 할까요?
DB_HOST=localhost
# DB_USER=root
DB_USER=test
# DB_PASS=1234
DB_PASS=5678
그럴 때는 config()
함수를 호출 시 path
옵션에 해당 파일 경로를 넘기면 됩니다.
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
console.log('DB_HOST:', process.env.DB_HOST);
console.log('DB_USER:', process.env.DB_USER);
console.log('DB_PASS:', process.env.DB_PASS);
$ node index.mjs
DB_HOST: localhost
DB_USER: test
DB_PASS: 5678
프로그램을 실행하면서 환경변수 불러오기
dotenv
를 임포트(import)하여 dotenv.config()
함수를 코드에서 호출하기 힘든 상황이라면,
프로그램을 구동할 때, node
커맨드의 -r
또는 --require
옵션으로 dotenv/config
를 넘기는 방법도 있는데요
이 방법을 사용하면 dotenv
라이브러리를 코드에 직접 임포트하지 않아도 .env
파일에 저장된 환경 변수가 process.env
에 설정됩니다.
우선 index.js
나 index.mjs
파일을 열고 dontenv
패키지를 불러와서 dotenv.config()
함수를 호출하는 부분을 삭제하고요.
console.log("DB_HOST:", process.env.DB_HOST);
console.log("DB_USER:", process.env.DB_USER);
console.log("DB_PASS:", process.env.DB_PASS);
-r
옵션으로 dotenv/config
를 넘겨서 실행을 해보면 정상적으로 환경 변수가 출력되는 것을 볼 수 있습니다.
$ node -r dotenv/config index.mjs
DB_HOST: localhost
DB_USER: root
DB_PASS: 1234
만약에 .env
가 아닌 다른 경로에 있는 파일에 환경 변수를 저장해놨다면 DOTENV_CONFIG_PATH
환경 변수를 사용하면 됩니다.
$ DOTENV_CONFIG_PATH=.env.local node -r dotenv/config index.mjs
DB_HOST: localhost
DB_USER: test
DB_PASS: 5678
이 방법은 어떤 프로젝트가 CommonJS 기반인지 ES 모듈 기반인지 미리 알 수 없을 때 매우 유용합니다. 왜냐하면 해당 NOde.js 런타임(runtime)이 어떤 모듈 시스템을 사용하든지 상관없이 통하는 방법이기 때문입니다.
ES 모듈에서 발생하기 쉬운 실수
ES 모듈을 사용할 때는 CommonJS를 사용할 때 보다 좀 더 주의가 필요한데요. 흔히 발생하는 문제를 재현해보겠습니다.
아래 코드를 보시면, dotenv
라이브러리를 제일 임포트하기 때문에 db.js
파일이 process.env
에 접근할 때 환경 변수가 설정이 되어 있을 것 같습니다.
export const db_host = process.env.DB_HOST;
export const db_user = process.env.DB_USER;
export const db_pass = process.env.DB_PASS;
import dotenv from "dotenv";
import { db_host, db_user, db_pass } from "./db.js";
dotenv.config();
console.log("DB_HOST:", process.env.DB_HOST);
console.log("DB_USER:", process.env.DB_USER);
console.log("DB_PASS:", process.env.DB_PASS);
console.log({ db_host, db_user, db_pass });
하지만 실제 실행을 해보면 db.js
파일이 process.env
에 접근했을 시점에는 환경 변수가 설정이 되어 있지 않았던 것을 알 수 있습니다.
$ node index2.mjs
DB_HOST: localhost
DB_USER: root
DB_PASS: 1234
{ db_host: undefined, db_user: undefined, db_pass: undefined }
이러한 현상이 발생하는 이유는 dotenv.config()
함수가 db.js
파일이 임포트 된 이후에 호출되었기 때문인데요.
이 문제는 dotenv
라이브러리를 임포트하는 코드를 별도의 파일로 빼고, 그 안에서 dotenv.config()
함수를 호출하면 피할 수 있습니다.
import dotenv from "dotenv";
dotenv.config();
import "./env.js";
import { db_host, db_user, db_pass } from "./db.js";
console.log("DB_HOST:", process.env.DB_HOST);
console.log("DB_USER:", process.env.DB_USER);
console.log("DB_PASS:", process.env.DB_PASS);
console.log({ db_host, db_user, db_pass });
이제 다시 프로그램을 실행을 해보면 환경 변수가 모든 파일에서 정상적으로 읽히는 것을 볼 수 있습니다.
$ node index2.mjs
DB_HOST: localhost
DB_USER: root
DB_PASS: 1234
{ db_host: 'localhost', db_user: 'root', db_pass: '1234' }
이처럼 프로그램을 시작된 후 가급적 dotenv.config()
함수를 빨리 호출하는 것이 안전합니다.
이미 설정되어 있는 환경 변수
운영 체제 수준에서 이미 설정되어 있는 환경 변수는 dotenv를 통해 파일에서 읽어온 환경 변수 값들로 덮어써지지 않으니 주의가 필요한데요.
예를 들어, 리눅스 계열 운영체제에서 다음과 같이 프로그램을 실행하기 전에 미리 DB_PASS
환경 변수를 설정해놓으면
$ export DB_PASS=0000
$ node index.mjs
.env
파일에 설정해놓은 1234
가 무시되고 0000
이 적용되는 것을 볼 수 있습니다.
$ node index.mjs
DB_HOST: localhost
DB_USER: root
DB_PASS: 0000
참고로 어느 환경 변수가 이미 설정되어 있었는지는 debug
옵션을 true
로 주면 쉽게 알아낼 수 있습니다.
import dotenv from 'dotenv';
const result = dotenv.config({ debug: true });
console.log('DB_HOST:', process.env.DB_HOST);
console.log('DB_USER:', process.env.DB_USER);
console.log('DB_PASS:', process.env.DB_PASS);
$ node index.mjs
[dotenv@16.0.3][DEBUG] "DB_PASS" is already defined in `process.env` and was NOT overwritten
DB_HOST: localhost
DB_USER: root
DB_PASS: 0000
.env
파일에 설정해놓은 환경 변수의 값이 기 설정된 환경 변수의 값을 덮어쓰기를 원한다면 (좋은 관행은 아닙니다) override
를 true
로 설정하면 됩니다.
import dotenv from 'dotenv';
const result = dotenv.config({ debug: true, override: true });
console.log('DB_HOST:', process.env.DB_HOST);
console.log('DB_USER:', process.env.DB_USER);
console.log('DB_PASS:', process.env.DB_PASS);
$ node index2.mjs
[dotenv@16.0.3][DEBUG] "DB_PASS" is already defined in `process.env` and was overwritten
DB_HOST: localhost
DB_USER: root
DB_PASS: 0000
보안상 주의 사항
.env
파일에는 보통 데이터베이스의 비밀번호나 서드파티(3rd-party) 서비스의 API 키와 같이 민감한 인증 정보가 들어가기 때문에 GitHub와 같은 코드 저장소(repository)에 올리면 상당히 위험할 수 있습니다.
특히 협업 프로젝트에서는 .gitignore
파일에 이용하여 개발자들이 실수로라도 코드 저장소에 올릴 수 없도록 설정해놓는 것이 바람직하겠습니다.
.env
.env.local
뿐만 아니라, .env.production
, .env.staging
, .env.qa
, .env.development
, .env.local
, .env.test
이런 식으로 각 배포(deploy) 환경 별로 환경 변수를 다른 파일에 저장해두고 사용하는 것도 심심치 않게 볼 수 있는데요.
이렇게 하면 위와 마찬가지 이유로 보안 이슈에 취약할 뿐만 아니라, 일반적으로 코드(code)와 설정(config)을 한 곳에서 관리하는 것은 좋지 않은 소프트웨어 개발 관행으로 여겨집니다.
따라서 .env
파일은 개발자가 로컬 환경에서 환경 변수를 설정해야 할 때만 제한적으로 사용하는 것이 좋으며, 그 밖에 환경에서는 운영 체제 수준에서 제대로 환경 변수를 설정해줘야겠습니다.
전체 코드
본 포스팅에서 작성한 코드는 아래에서 직접 수정하거나 브라우저 상의 가상 터미널에서 실행해볼 수 있습니다.
마치면서
이상으로 dotenv
라이브러리를 이용해서 파일에 환경 변수를 저장해놓고 불러오는 방법에 대해서 자세히 살펴보았습니다.
dotenv
라이브러리에 대해서 좀 더 많은 내용이 알고 싶으시다면 GitHub 저장소를 방문해보시면 좋을 것 같습니다.