이 글에서 다루는 내용

Spring Boot에서 DB 비밀번호나 API 키 같은 민감한 설정값을 어떻게 관리해야 하는지, .env 파일과 OS 환경변수의 차이는 무엇인지, 로컬 개발 환경과 운영 서버에서 각각 어떤 방식이 적합한지를 실전 관점에서 정리한다.

Spring Boot의 환경변수 지원 방식

Spring Boot는 OS 환경변수를 네이티브로 지원한다. 별도 설정 없이 application.yml에서 ${변수명} 형태로 참조할 수 있다.

spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}

단, OS 환경변수는 전역(Global) 이기 때문에 시스템 전체에 영향을 미친다. 로컬에서 여러 프로젝트를 동시에 운영하거나, 서버에서 여러 JAR를 실행하는 경우 같은 변수명이 충돌할 수 있다.

로컬 개발 환경에서의 문제

OS 환경변수
DB_URL=jdbc:mysql://localhost:3306/projectA  ← 전역 설정

project-B 실행 시 → projectA의 DB를 바라봄 💥

로컬에서 프로젝트가 여러 개라면 OS 환경변수에 의존하는 방식은 적합하지 않다. 프로젝트별로 설정을 격리해야 한다.

해결 방법 1: application-local.yml

각 프로젝트 루트에 application-local.yml 파일을 두고 Git에서 제외한다.

project-A/
  application.yml
  application-local.yml   ← gitignore

project-B/
  application.yml
  application-local.yml   ← gitignore
./gradlew bootRun --args='--spring.profiles.active=local'

팀 공유용으로는 application-local.yml.example을 Git에 올려두는 방식이 일반적이다.

해결 방법 2: .env 파일

프로젝트 폴더 안에 .env 파일을 두면 프로젝트별로 격리된다.

project-A/
  .env   ← project-A 전용

project-B/
  .env   ← project-B 전용

Spring Boot는 .env를 직접 읽지 못하므로 두 가지 방법 중 하나를 선택한다.

방법 A: 실행 스크립트에서 source로 로드 (의존성 없음)

set -a
source .env
set +a
java -jar service.jar

방법 B: spring-dotenv 라이브러리 사용

implementation 'me.paulschwarz:spring-dotenv:4.0.0'

의존성만 추가하면 .env 파일을 Spring PropertySource로 자동 등록해서 application.yml에서 바로 참조할 수 있다.

운영 서버에서의 .env 사용

운영 서버에서도 .env를 사용하는 것은 실용적인 선택이다. 다만 보안 관리가 핵심이다.

파일 권한 설정 필수

chmod 600 .env
chown app-user:app-user .env

서비스별 폴더로 격리

/app/
  service-a/
    service-a.jar
    .env

  service-b/
    service-b.jar
    .env

컨테이너 없이 여러 JAR를 직접 실행하는 환경에서도 폴더 구조로 충돌 없이 관리할 수 있다.

systemd로 환경변수 관리하기

리눅스 운영서버에서 JAR를 systemd 서비스로 등록해서 실행하는 경우, systemd 자체에서 환경변수를 주입할 수 있다.

방법 1: service 파일에 직접 선언

# /etc/systemd/system/service-a.service
[Unit]
Description=Service A
After=network.target

[Service]
User=app-user
WorkingDirectory=/app/service-a
ExecStart=java -jar /app/service-a/service-a.jar
Environment="DB_URL=jdbc:mysql://localhost:3306/db_a"
Environment="DB_PASSWORD=secret"
Environment="JWT_SECRET=mysecret"
Restart=always

[Install]
WantedBy=multi-user.target

변수가 많아지면 관리가 힘들고, service 파일 자체가 민감 정보를 담게 되는 단점이 있다.

방법 2: EnvironmentFile로 .env 분리 (권장)

service 파일에서 .env 파일을 참조하는 방식으로, 설정과 민감 정보를 분리할 수 있다.

# /etc/systemd/system/service-a.service
[Unit]
Description=Service A
After=network.target

[Service]
User=app-user
WorkingDirectory=/app/service-a
EnvironmentFile=/app/service-a/.env
ExecStart=java -jar /app/service-a/service-a.jar
Restart=always

[Install]
WantedBy=multi-user.target
# /app/service-a/.env
DB_URL=jdbc:mysql://localhost:3306/db_a
DB_PASSWORD=secret
JWT_SECRET=mysecret

.env 파일 권한도 꼭 설정한다.

chmod 600 /app/service-a/.env
chown app-user:app-user /app/service-a/.env

서비스별로 service 파일과 .env가 분리되어 있어서 멀티 JAR 환경에서도 충돌 없이 관리된다.

/etc/systemd/system/
  service-a.service   ← EnvironmentFile=/app/service-a/.env 참조
  service-b.service   ← EnvironmentFile=/app/service-b/.env 참조

/app/
  service-a/
    service-a.jar
    .env

  service-b/
    service-b.jar
    .env

systemd 서비스 등록 및 실행

# 서비스 등록 및 시작
systemctl daemon-reload
systemctl enable service-a
systemctl start service-a

# 상태 확인
systemctl status service-a

# 로그 확인
journalctl -u service-a -f

서버 재부팅 시 자동 실행되고, 프로세스 종료 시 자동 재시작(Restart=always)되기 때문에 운영 서버에서 JAR를 직접 실행하는 경우 systemd로 관리하는 것이 표준적인 방식이다.

JAR 실행 시 환경변수 주입 vs .env 비교

환경변수 직접 주입

DB_URL=jdbc:mysql://localhost:3306/db \
DB_PASSWORD=secret \
java -jar service.jar

변수가 많아지면 명령이 길어지고, history 명령으로 비밀번호가 노출될 수 있다는 단점이 있다.

.env 파일 사용

# .env
DB_URL=jdbc:mysql://localhost:3306/db
DB_PASSWORD=secret
JWT_SECRET=mysecret

# 실행
java -jar service.jar

변수가 많아도 깔끔하게 관리되고, 히스토리에 비밀번호가 남지 않는다.

선택 기준

상황 추천 방식
변수 3개 이하, 단순 실행 환경변수 직접 주입
변수 많음, 멀티 서비스 .env 파일
보안이 엄격한 운영 환경 AWS Secrets Manager / Vault

규모별 Secret 관리 전략

소규모   →  .env 파일로 충분
중규모   →  Docker Secret / K8s Secret
대규모   →  AWS Secrets Manager / HashiCorp Vault / GCP Secret Manager

Docker나 Kubernetes 환경에서는 컨테이너 또는 Pod 단위로 환경변수가 격리되기 때문에 OS 환경변수 충돌 문제가 자연스럽게 해소된다.