환경 변수(.env)란 무엇이고 왜 중요한가?

환경 변수(.env)란 무엇이고 왜 중요한가?

코드를 작성하다 보면 데이터베이스 비밀번호, API 키, 서버 주소 같은 값들을 어딘가에 적어야 하는 순간이 옵니다. 처음에는 코드 안에 직접 넣게 됩니다. 데이터베이스 접속 정보를 코드에 하드코딩하고 잘 돌아가니까 넘어갑니다. 그런데 이 코드를 GitHub에 올리는 순간 비밀번호가 전 세계에 공개됩니다. 실제로 GitHub에 AWS 키를 올렸다가 몇 분 만에 수백만 원이 과금된 사례도 있습니다. 환경 변수는 이런 문제를 해결하는 가장 기본적인 방법입니다. 이번 글에서는 환경 변수가 무엇인지, 왜 중요한지, 실제로 어떻게 관리하는지를 정리해보겠습니다.

환경 변수란 무엇인가

환경 변수는 운영체제나 실행 환경에서 프로그램에게 전달하는 설정값입니다. 프로그램 코드 바깥에 존재하면서 프로그램이 실행될 때 참조할 수 있는 값입니다.

리눅스 터미널에서 환경 변수를 확인해볼 수 있습니다.

echo $HOME
/home/ubuntu

echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

HOME은 현재 사용자의 홈 디렉토리 경로이고, PATH는 명령어를 찾을 디렉토리 목록입니다. 이런 값들이 환경 변수입니다. 운영체제가 미리 설정해둔 것도 있고, 사용자가 직접 만들 수도 있습니다.

터미널에서 환경 변수를 만들고 사용하는 건 간단합니다.

export DB_PASSWORD=mysecret
echo $DB_PASSWORD
mysecret

export로 변수를 설정하면 현재 셸 세션에서 실행되는 프로그램들이 이 값을 읽을 수 있습니다. Node.js에서는 process.env.DB_PASSWORD로, Python에서는 os.environ[‘DB_PASSWORD’]로 접근합니다.

코드에 직접 값을 넣으면 안 되는 이유

환경 변수가 왜 필요한지를 이해하려면 코드에 직접 값을 넣었을 때 어떤 문제가 생기는지를 알아야 합니다.

첫 번째 문제는 보안입니다. 데이터베이스 비밀번호나 API 키를 코드에 적으면 코드가 곧 비밀번호입니다. 이 코드를 GitHub에 올리면 해당 정보가 저장소에 접근할 수 있는 모든 사람에게 노출됩니다. 공개 저장소라면 전 세계에 공개되는 겁니다. GitHub에는 공개 저장소를 자동으로 스캔해서 API 키를 찾아내는 봇이 돌아다닙니다. AWS 키가 노출되면 몇 분 안에 암호화폐 채굴에 악용되어 엄청난 과금이 발생하는 사례가 실제로 있습니다.

한 번 커밋에 포함된 비밀번호는 커밋을 삭제해도 git 히스토리에 남아 있습니다. 완전히 제거하려면 별도의 작업이 필요하고, 이미 누군가가 본 후라면 의미가 없습니다. 처음부터 코드에 넣지 않는 게 최선입니다.

두 번째 문제는 환경별 설정 차이입니다. 개발할 때는 로컬 데이터베이스에 접속하고, 테스트 서버에서는 테스트 데이터베이스에 접속하고, 운영 서버에서는 운영 데이터베이스에 접속해야 합니다. 접속 주소와 비밀번호가 전부 다릅니다. 이 값들이 코드에 박혀 있으면 환경마다 코드를 수정해야 합니다. 배포할 때마다 개발용 주소를 운영용 주소로 바꾸고, 개발 환경으로 돌아오면 다시 바꾸는 건 실수가 나기 딱 좋은 구조입니다.

세 번째 문제는 협업입니다. 팀원마다 로컬 환경이 다를 수 있습니다. 데이터베이스 비밀번호가 다르거나 API 키가 개인별로 다를 수 있는데, 이 값이 코드에 있으면 팀원 한 명이 자기 설정으로 커밋할 때마다 다른 사람의 환경이 깨집니다.

환경 변수를 사용하면 이 세 가지 문제가 전부 해결됩니다. 코드에는 변수 이름만 적고, 실제 값은 각 환경에서 따로 설정합니다. 코드를 아무리 공개해도 비밀번호는 노출되지 않고, 환경마다 다른 값을 넣을 수 있고, 팀원마다 자기 설정을 독립적으로 관리할 수 있습니다.

.env 파일이란

터미널에서 export로 환경 변수를 설정하는 건 셸을 닫으면 사라집니다. 매번 서버에 접속할 때마다 설정하는 것도 번거롭습니다. 그래서 환경 변수를 파일로 관리하는 방법이 생겼고, 그 파일이 .env입니다.

.env 파일은 프로젝트 루트 디렉토리에 만들고, 키=값 형태로 한 줄씩 적습니다.

DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=mysecretpassword
DB_NAME=myapp

REDIS_HOST=localhost
REDIS_PORT=6379

API_KEY=sk-abc123def456
NODE_ENV=development
PORT=3000

파일 이름 앞에 점이 붙어 있기 때문에 리눅스에서는 숨김 파일로 취급됩니다. ls만 치면 안 보이고 ls -a를 해야 보입니다.

Node.js에서 .env 파일을 사용하려면 dotenv 패키지를 설치합니다.

npm install dotenv

코드 맨 위에서 불러오면 .env 파일의 내용이 환경 변수로 로드됩니다.

javascript

require('dotenv').config();

const dbHost = process.env.DB_HOST;
const dbPassword = process.env.DB_PASSWORD;
```

Python에서는 python-dotenv 패키지를 사용합니다.
```
pip install python-dotenv

python

from dotenv import load_dotenv
import os

load_dotenv()

db_host = os.getenv('DB_HOST')
db_password = os.getenv('DB_PASSWORD')
```

이렇게 하면 코드에는 변수 이름만 있고 실제 값은 .env 파일에서 가져옵니다. .env 파일만 환경에 맞게 바꾸면 코드는 수정할 필요가 없습니다.

.env 파일은 절대 저장소에 올리면 안 된다

.env 파일에는 비밀번호와 키가 들어 있기 때문에 Git 저장소에 올리면 안 됩니다. 프로젝트의 .gitignore 파일에 반드시 추가해야 합니다.
```
# .gitignore
.env
.env.local
.env.production
```

대신 .env.example이라는 파일을 만들어서 저장소에 올립니다. 실제 값 대신 어떤 변수가 필요한지 형식만 적어둡니다.
```
DB_HOST=
DB_PORT=3306
DB_USER=
DB_PASSWORD=
DB_NAME=

REDIS_HOST=
REDIS_PORT=6379

API_KEY=
NODE_ENV=development
PORT=3000
```

새로운 팀원이 프로젝트를 받으면 .env.example을 복사해서 .env로 만들고 자기 환경에 맞는 값을 채우면 됩니다. 어떤 환경 변수가 필요한지 문서를 따로 만들 필요 없이 .env.example 파일이 곧 문서 역할을 합니다.

환경별 .env 파일 관리

프로젝트가 커지면 환경별로 다른 설정이 필요합니다. 개발, 테스트, 운영 환경에서 각각 다른 값을 사용해야 합니다.

흔한 방법은 환경별로 .env 파일을 나누는 겁니다.
```
.env                 # 기본값 또는 로컬 개발용
.env.development     # 개발 환경
.env.test            # 테스트 환경
.env.production      # 운영 환경

프레임워크에 따라 환경별 .env 파일을 자동으로 로드하는 기능이 있습니다. Next.js는 NODE_ENV에 따라 해당 환경의 .env 파일을 자동으로 읽습니다. 직접 구현해야 하는 경우에는 dotenv에서 파일 경로를 지정할 수 있습니다.

javascript

require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });

운영 환경의 .env 파일은 서버에 직접 만들거나 CI/CD 파이프라인에서 시크릿으로 관리합니다. 이전 글에서 다뤘던 GitHub Actions의 시크릿 기능이 이런 용도입니다.

Docker 환경에서의 환경 변수

이전 글에서 다뤘던 Docker Compose에서도 환경 변수를 여러 방식으로 사용합니다.

docker-compose.yml에서 직접 지정하는 방법이 있습니다.

yaml

services:
  app:
    image: my-app
    environment:
      - NODE_ENV=production
      - DB_HOST=db

.env 파일을 통째로 전달하는 방법도 있습니다.

yaml

services:
  app:
    image: my-app
    env_file:
      - .env
```

Docker에서 환경 변수가 중요한 이유가 하나 더 있습니다. Docker 이미지는 한 번 빌드하면 여러 환경에서 동일하게 사용하는 게 원칙입니다. 개발용 이미지와 운영용 이미지를 따로 빌드하는 게 아니라 하나의 이미지를 만들고, 환경 변수로 동작을 다르게 합니다. 같은 이미지가 환경 변수에 따라 개발 모드로도, 운영 모드로도 동작하는 구조입니다.

자주 사용하는 환경 변수 이름 관례

환경 변수 이름을 짓는 데 정해진 규칙은 없지만 관례가 있습니다. 대문자와 언더스코어를 사용하는 게 일반적입니다.

데이터베이스 관련은 보통 DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME 같은 이름을 씁니다. DATABASE_URL처럼 접속 정보를 하나의 문자열로 합쳐서 쓰는 경우도 있습니다. PostgreSQL이나 일부 프레임워크에서는 이 형식을 선호합니다.
```
DATABASE_URL=mysql://root:password@localhost:3306/myapp
```

앱 설정은 NODE_ENV, PORT, APP_SECRET 같은 이름을 씁니다. NODE_ENV는 development, test, production 중 하나의 값을 가지는 게 관례이고, 많은 라이브러리가 이 값을 보고 동작을 바꿉니다.

외부 서비스 키는 서비스명을 접두어로 붙이는 게 일반적입니다. AWS_ACCESS_KEY_ID, STRIPE_SECRET_KEY, SENDGRID_API_KEY 같은 식입니다. 접두어가 있으면 어떤 서비스의 키인지 바로 알 수 있습니다.

환경 변수에 넣으면 안 되는 것

환경 변수에 모든 설정을 넣는 건 좋지 않습니다. 환경 변수는 간단한 문자열 값에 적합하지, 복잡한 구조의 데이터를 넣기에는 맞지 않습니다.

긴 JSON 객체나 여러 줄에 걸친 설정은 환경 변수보다 설정 파일로 관리하는 게 낫습니다. 환경 변수에 JSON을 넣으면 이스케이프 문제가 생기기 쉽고 가독성도 떨어집니다.

SSL 인증서 내용이나 SSH 키 같은 긴 텍스트도 환경 변수에 직접 넣기보다는 파일 경로를 환경 변수로 지정하는 게 일반적입니다.
```
SSL_CERT_PATH=/etc/ssl/certs/my-cert.pem
SSH_KEY_PATH=/home/ubuntu/.ssh/id_rsa
```

환경 변수에는 접속 정보, 키, 모드 설정 같은 짧고 단순한 값을 넣고, 복잡한 설정은 별도 파일로 관리하는 게 깔끔합니다.

보안 관련 주의사항

.env 파일을 .gitignore에 추가하는 것만으로는 보안이 완전하지 않습니다. 몇 가지 더 신경 써야 할 부분이 있습니다.

서버에 있는 .env 파일의 권한을 확인해야 합니다. 이전 글에서 다뤘던 chmod를 사용해서 소유자만 읽을 수 있게 설정합니다.
```
chmod 600 .env

다른 사용자가 .env 파일을 읽을 수 없게 해야 합니다.

로그에 환경 변수 값이 출력되지 않도록 주의해야 합니다. 디버깅 목적으로 환경 변수를 전부 출력하는 코드를 넣었다가 로그 파일에 비밀번호가 기록되는 경우가 있습니다. 로그에 민감한 정보가 남으면 로그 파일이 유출됐을 때 함께 노출됩니다.

API 키나 비밀번호는 주기적으로 교체하는 게 좋습니다. 키가 유출됐을 때 피해를 줄이려면 하나의 키를 오래 사용하는 것보다 주기적으로 바꾸는 게 안전합니다.

프로젝트 규모가 커지면 AWS Secrets Manager, HashiCorp Vault 같은 전문 시크릿 관리 도구를 도입하기도 합니다. 이런 도구들은 환경 변수 값을 암호화해서 저장하고, 접근 권한을 세밀하게 관리하고, 키 교체를 자동화해줍니다. 개인 프로젝트나 소규모 팀에서는 .env 파일만으로 충분하지만, 서비스가 커지면 검토해볼 만합니다.

마무리

환경 변수는 코드와 설정을 분리하는 가장 기본적인 방법입니다. 비밀번호와 키를 코드 밖으로 빼내서 보안을 지키고, 환경마다 다른 값을 주입해서 하나의 코드로 여러 환경을 대응합니다. .env 파일에 값을 정리하고, .gitignore에 추가하고, .env.example로 형식을 공유하는 게 기본 패턴입니다. 간단하지만 이 습관 하나가 보안 사고를 예방하고 팀 협업을 원활하게 만듭니다. 다음 글에서는 서버에 실제 애플리케이션을 배포하는 전체 과정을 처음부터 끝까지 정리해보겠습니다.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *