환경 변수 (Environment Variable) 이해하기

4/10/2023

환경 변수(Environment Variable)에 대해 얘기해 보려 해요. 환경 변수는 프로세스(process)에 붙는 key-value 값들이에요. 우리에게 가장 익숙한 건 NODE_ENV 같은 게 있죠. 근데 우리가 일상적으로 사용하는 컨텍스트를 잠깐 벗어나 볼게요. 터미널에서 env 라는 명령어를 입력하면 다음과 같은 결과가 나와요.

image

우리가 bash, zsh, fish 등등의 shell 이라 통칭되는 것들을 사용하는데요. 이 shell 도 터미널 앱에서 실행시켜주는 하나의 프로세스인데요. 우리가 입력하는 걸 터미널 프로세스가 쉘 프로세스에게 전달하면, 쉘 프로세스가 파싱해서 처리하고 결과를 출력하죠. 사용하는 쉘에 따라 ~/.bash_profile, ~/.zshrc 를 비롯한 설정 파일로부터 몇몇 환경변수가 세팅되고, 시스템 글로벌한 값들도 세팅이 되죠. 그래서 env 를 실행시키면 저런 많은 key-value 들이 보이는 거예요.

한 프로세스가 갖고 있는 환경 변수는 그 child process 에도 그대로 전달돼요. 테스트 하나 해볼까요. 예를 들어 저 SHELL 이란 변수 값을 받아볼까요.

image

이런 파일을 하나 만들고 node test.js를 실행하면 값이 제대로 출력되는 걸 볼 수 있어요.

image

process가 Node.js 에서 제공되는 글로벌 오브젝트인데요. 현재 프로세스에 대한 정보를 담고 있고 환경 변수도 그 중 하나죠.

만약 새로운 환경 변수를 추가해서 저 스크립트를 실행하고 싶다면 어떡할까요?

image

image

실행하려던 명령어 맨 앞에 KEY1=VAL1 KEY2=VAL2 ... the-real-command 형태로 실행하시면 그 실행되는 프로세스에 해당 환경 변수가 추가돼요. 이건 Node.js 의 기능이 아니라 쉘의 원래 기능이에요.

호스팅 플랫폼(Vercel, Netlify, …)을 보면 환경 변수 설정하는 화면들이 꼭 있는데요. 거기에 지정을 해주면 여러분이 만드신 앱을 실행할 때, 그 플랫폼들에서 저런 식으로 환경 변수를 붙여준다고 생각하시면 이해가 조금 쉬울 거예요.

무슨 API_KEY 같은 환경 변수를 호스팅 플랫폼에 추가했다고 치죠. 로컬에서 개발할 때 항상 저렇게 환경 변수를 명령에 덧붙일 수 있겠지만, 아무래도 번거롭죠. 그래서 우리는 보통 .env 혹은 .env.local 같은 파일을 만들고, 그 안에 원하는 값을 넣되 그 파일은 repository 에서 제외하죠 (중요한 키값을 커밋하면 위험하니까요) 그런데 저 파일을 생성했다고 우리의 test.js 파일이 저 값을 알아서 읽어오진 못하거든요. 그래서 우린 보통 dotenv 같은 라이브러리를 사용합니다. 이 라이브러리가 별다른 걸 하는 건 아니고, .env 파일 등을 읽어서 그 key-value 를 process.env[key] = value 해주는 거예요.

그러면 로컬 개발할 때는 .env 파일에서 읽어온 값을 사용하고, 배포된 버전은 호스팅 플랫폼에서 환경 변수를 붙여주니 .env 파일 없이도 잘 동작하고요. 여기까지는 명확한데요. 여기에 프론트엔드가 끼면서 좀 문제가 생겨요. 서버 코드에서 process.env.DATABASE_URL 같은 값을 써서 데이터베이스에 접속하는 등의 코드는 자연스러운데요. 프론트엔드에서도 이런 식으로 사전에 정의된 변수값을 쓰고 싶을 때가 있어요. 코드 안에 선언하진 않고 외부에서 전달해 주는 값이 필요한 상황이요. 예를 들면 API_ENDPOINT 같은 변수에 개발 중일 땐 http://localhost:3000, 배포된 버전에는 https://my-domain.com 이런 식으로 하고, 코드에서는 다음과 같이 접근할 수 있겠죠.

image

그런데 문제는, process는 Node.js 에 존재하지 브라우저에는 없는 객체거든요. Node.js 쪽에서 쓰던 프로세스의 환경 변수라는 개념을 브라우저에 가져는 오고 싶은데 process라는 오브젝트가 존재하지도 않고, 그 값을 유저 브라우저로 전달할 방법이 없는걸요. 그래서 사람들이 일종의 꼼수를 고안해 내기 시작합니다. 우리가 사용하는 번들러(bundler)가 최종 output 에다가 약간의 코드를 주입해 주는 거죠.

image

이런 코드를 주입해 주면, 그 이후 어떤 프론트엔드 코드에서든 process.env.API_ENDPOINT라는 변수를 쓸 수 있는 거죠.

어떤 번들러는 (혹은 어떤 번들러의 어떤 플러그인을 사용하면) 좀 다르게 처리하기도 하는데요. 저렇게 오브젝트를 밀어 넣는 대신에 아웃풋 파일 속 변수를 실제 값으로 바꿔치기 해버려요. 위의 fetch 구문이 아래처럼 되는 거죠.

image

이렇게 아예 값으로 바꿔치기하는 경우에 꽤 재밌는 일이 생기는데요. 만약에 다음과 같은 코드가 있다고 치면요.

image

번들러를 가지고 위 코드를 배포용으로 빌드 하면 다음과 같이 되겠죠 (NODE_ENV 가 “production” 일 테니까)

image

그러면 요즘 번들러는 위와 같이 절대 실행될 일 없는 코드를 아예 삭제해 주기까지 해요. 그리고 something-heavy 라이브러리를 사용하던 코드가 사라졌기 때문에 저 import 구문조차 삭제해도 문제가 없겠죠? 그래서 번들러가 그 부분까지 처리해 줍니다.

image

이런 과정을 tree-shaking 이라고도 불러요. 나무를 흔들면 떨어질 과일은 떨어지잖아요. 마찬가지로 코드에서도 흔들어서 떨궈낼 코드를 떨구는 거죠.

하지만 유의할 점은, 모든 환경 변수가 이렇게 프론트엔드 코드(HTML 혹은 JS)에 노출되어도 괜찮은 게 아니에요. 데이터베이스 주소, 비밀번호 같은 건 철저하게 서버 사이드에 남아야 하는 정보잖아요. 그런데 코딩을 하는 과정에 실수로 프론트엔드 코드에 process.env.DATABASE_URL 같은 표현이 쓰였는데, 번들러가 이 값을 배포되는 프론트엔드 코드에 담으면 심각한 보안 문제가 생기죠. 그럼 개발하는 우리가 ‘이 코드는 프론트엔드 부분이니 이 환경 변수를 쓰지 말고, 이 코드는 백엔드 부분이니 이 환경 변수를 써도 되고’ 이런 구분을 잘 해서 코드를 작성하는 게 중요하겠죠. 하지만 이게 실수하기 쉬운 부분이다 보니 번들러나 웬만한 프레임워크에서는 실수의 여지를 줄이기 위해 나름의 장치를 마련해놨어요.

Next.js 를 쓰는 경우 NEXT_PUBLIC_ 으로 시작하는 환경 변수는 프론트, 백 양쪽에 모두 노출될 수 있도록 되어 있고, 그렇지 않은 모든 환경 변수는 배포 과정에서 Next.js 가 프론트엔드 쪽 코드엔 저 값을 undefined 처리하고, serverless function 쪽에만 값을 제공해 주도록 내부 처리가 되어 있어요. 자세한 설명은 다음 링크를 참조하세요: https://nextjs.org/docs/basic-features/environment-variables

Vite 을 쓰는 경우, 브라우저로 노출되어야 하는 환경 변수는 VITE_ 라는 prefix 를 붙이도록 되어 있고요: https://vitejs.dev/guide/env-and-mode.html#env-files

Vite 의 경우 기존 번들러와 달리 process.env.XYZ 가 아닌 import.meta.env.XYZ 로 값을 얻어야 한다는 차이가 있는데요. 애초에 Node.js 에서만 존재하는 process 오브젝트를 브라우저 상에 가짜로 만들어 넣는 대신에 vite 은 이미 존재하는 import 를 활용하는 방향을 택한 거죠.

다시 정리하자면, 사용하시는 프레임워크와 배포하는 호스팅 플랫폼에 따라 환경 변수를 사용하는 방식이 달라질 수 있어요. 어떤 경우엔 process.env.XYZ 로 접근하면 미리 process.env = { ... } 로 채워진 오브젝트에 의해 동작하기도 하고, 어떤 경우에는 빌드 스크립트가 코드 내에 그 변수 사용하는 부분을 값으로 아예 static 하게 치환해버리리도 하고, 서버에서 도는 프로세스의 경우에는 호스팅 플랫폼이 환경 변수를 그 프로세스에 설정해 줌으로써 그냥 process.env.XYZ 로 접근할 수 있기도 하고요.

이게 어려운 개념이라기보다 간단한 개념을 여기저기 사용하면서 헷갈릴 요소가 많아진 상황이라 이해하시면 될 것 같아요. 여전히 헷갈리는 점이 있다면 꼭 질문 주세요 😊


시나브로 자바스크립트는, 이유를 모르고 사용해 오던 굵직한 주제들에 대해 깊이 있게 설명해주는 강좌입니다. 자바스크립트 생태계의 구성 요소를 이해하고, 프레임워크 없이 바닥부터 다양한 토픽을 구현해보면서 이해도를 높이고, 어떤 프레임워크든 쉽게 이해할 수 있는 기반과 자신감을 다져주는 것을 목표로 합니다.