신규 서비스 배포를 앞두고 운영팀에서 연락이 왔습니다. "스테이징에서는 잘 됐는데 운영에서 오류가 납니다." 로그를 열어보니 운영 서버가 개발 DB에 연결 시도하고 있었습니다. 개발자가 application.yml 하나에 모든 환경 설정을 때려넣고, 프로파일 지정 없이 배포했던 것입니다.
이 모듈은 그런 사고를 막기 위한 것입니다. 환경별 설정 파일을 어떻게 분리하고, 민감한 정보를 어디에 두어야 하며, 운영 서버에서 설정을 어떻게 적용하는지를 다룹니다.
- 1application-dev.yml / application-stg.yml / application-prod.yml 파일 분리 구조를 설명할 수 있다
- 2OS 환경변수 > JVM 옵션 > properties 파일의 우선순위를 이해하고 활용할 수 있다
- 3.env 파일, 환경변수, Vault의 용도 차이를 설명하고 .gitignore에 포함할 파일을 알 수 있다
- 4Tomcat setenv.sh로 JVM 환경변수를 주입하는 방법을 실습할 수 있다
- 5설정 변경 후 무중단 적용 여부를 판단하고 올바른 재기동 절차를 선택할 수 있다
환경 분리 — 왜 파일을 나눠야 하나
환경별 설정 파일 분리
개발/스테이징/운영은 DB 주소, 로그 레벨, API 엔드포인트가 모두 다릅니다. 하나의 파일에 모든 환경 설정을 넣으면 배포 실수가 났을 때 어느 환경이 잘못 연결됐는지 추적하기 어렵고, 민감한 운영 정보가 개발 환경에 노출될 위험도 생깁니다.

Spring Boot는 application-{profile}.yml 규칙으로 환경별 파일을 자동으로 읽습니다.
src/main/resources/
├── application.yml # 공통 설정 (프로파일 무관하게 항상 로드)
├── application-dev.yml # 개발 환경 전용
├── application-stg.yml # 스테이징 환경 전용
└── application-prod.yml # 운영 환경 전용
# application.yml (공통)
spring:
application:
name: my-service
jpa:
open-in-view: false
logging:
pattern:
console: "%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb_dev
username: dev_user
password: dev_pass # 개발 환경은 .env나 로컬 파일 허용
jpa:
show-sql: true # 개발에서만 SQL 로그 출력
logging:
level:
root: DEBUG
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:3306/${DB_NAME} # 환경변수로 주입
username: ${DB_USER}
password: ${DB_PASSWORD}
jpa:
show-sql: false # 운영에서는 SQL 로그 off
logging:
level:
root: WARN
핵심 원칙: 운영 설정 파일(application-prod.yml)에는 실제 비밀값을 절대 하드코딩하지 않습니다. ${환경변수명} 플레이스홀더만 두고, 실제 값은 OS 환경변수나 Vault에서 주입합니다.
설정 우선순위 — 어디서 값이 오는가
Spring Boot 설정 우선순위
같은 키가 여러 곳에 정의되어 있을 때 어떤 값이 적용되는지 알아야 설정 충돌을 추적할 수 있습니다. Spring Boot는 명확한 우선순위를 따릅니다.
높음 ──────────────────────────────────────────
1. OS 환경변수 (SPRING_DATASOURCE_URL)
2. JVM 시스템 옵션 (-Dspring.datasource.url=...)
3. 프로파일 yml 파일 (application-prod.yml)
4. application.yml (공통 파일)
낮음 ──────────────────────────────────────────
OS 환경변수가 가장 강하다는 것이 핵심입니다. 파일에 값이 있더라도 환경변수를 설정하면 그 값이 우선 적용됩니다. 운영 서버에서 DB 비밀번호를 파일에 넣지 않고 환경변수로 주입하는 이유가 여기 있습니다.
# 운영 서버에서 환경변수 확인
env | grep -E "SPRING|DB|APP|JAVA"
# 실행 중인 JVM 프로세스의 환경변수 확인 (PID는 ps aux로 찾기)
cat /proc/$(pgrep -f myapp.jar)/environ | tr '\0' '\n' | grep -E "DB|SPRING"
민감 정보 관리 — .env, 환경변수, Vault
비밀값을 어디에 두어야 하나
DB 비밀번호, API 키, JWT 시크릿 같은 민감 정보는 코드와 분리해야 합니다. 규모와 보안 요구에 따라 세 가지 방법 중 하나를 선택합니다.
| 방법 | 적합한 상황 | 주의사항 |
|---|---|---|
.env 파일 | 개발 로컬 / 소규모 스테이징 | git에 절대 커밋 금지, .gitignore 필수 |
| OS 환경변수 | 단일 서버 운영 환경 | setenv.sh 또는 systemd 서비스 파일에 주입 |
| HashiCorp Vault | 멀티 서버/클라우드/보안 규정 있는 운영 | 별도 Vault 서버 구성 필요, 접근 권한 관리 |
# .env 파일 예시 (로컬 개발용)
DB_HOST=localhost
DB_NAME=mydb_dev
DB_USER=dev_user
DB_PASSWORD=dev_secret_password
APP_SECRET_KEY=localdev_secret_key_only
.gitignore 에 반드시 포함할 파일들:
# .gitignore
.env
.env.*
!.env.example # 예시 파일(실제 값 없음)은 허용
application-prod.yml # 운영 설정에 비밀이 있을 경우
*.properties.bak
/config/secrets/
# .env.example (저장소에 포함하는 템플릿)
DB_HOST=localhost
DB_NAME=
DB_USER=
DB_PASSWORD= # 팀원이 이 파일을 복사해 .env 를 만든다
APP_SECRET_KEY=

Tomcat 환경변수 주입 — setenv.sh
setenv.sh 로 JVM 환경변수 설정
Tomcat은 기동 시 $CATALINA_HOME/bin/setenv.sh 파일이 있으면 자동으로 실행합니다. 이 파일에 환경변수와 JVM 옵션을 설정하면 Tomcat 프로세스 전체에 적용됩니다. 파일이 없으면 직접 만들면 됩니다.
# /opt/tomcat/bin/setenv.sh
export JAVA_OPTS="
-server
-Xms512m
-Xmx2048m
-Dspring.profiles.active=prod
-Dspring.config.location=/etc/myapp/application-prod.yml
"
# 민감 정보는 OS 환경변수에서 읽어서 JVM에 전달
export JAVA_OPTS="$JAVA_OPTS
-DDB_HOST=${DB_HOST}
-DDB_PASSWORD=${DB_PASSWORD}
"
# setenv.sh 적용 확인 — Tomcat 재기동 후
ps aux | grep java | grep spring.profiles.active
# 또는 실행 중인 프로세스 JVM 옵션 확인
jps -v | grep Catalina
setenv.sh 권한 설정:
chmod 750 /opt/tomcat/bin/setenv.sh
chown tomcat:tomcat /opt/tomcat/bin/setenv.sh
실습
실행 중인 환경에서 어떤 설정 관련 환경변수가 있는지 확인합니다. 운영 서버라면 DB 연결 정보, 프로파일 설정 등이 나와야 합니다. 아무것도 나오지 않는다면 환경변수 주입이 누락된 상태입니다.
# 실행 중인 Java 프로세스의 환경변수 직접 확인
# 먼저 PID 찾기
ps aux | grep java | grep -v grep | awk '{print $2}'
# PID 로 환경변수 읽기 (예: PID=12345)
cat /proc/12345/environ | tr '\0' '\n' | grep -E "DB|SPRING|APP"
SPRING_PROFILES_ACTIVE=prod
DB_HOST=db-prod.internal
DB_NAME=mydb_prod
DB_USER=app_user
env | grep -E 'JAVA|SPRING|DB|APP'- SPRING_PROFILES_ACTIVE 또는 -Dspring.profiles.active 에 prod(또는 의도한 환경)가 설정돼 있는가
- DB_HOST, DB_NAME 같은 연결 정보 환경변수가 올바른 환경을 가리키는가
- .env 파일이 있다면 ls -la .env 로 git 트래킹 여부 확인 — git ls-files .env 가 빈 결과여야 한다
- cat /opt/tomcat/bin/setenv.sh 에 민감 정보가 하드코딩돼 있지 않은가
Tomcat을 사용하는 서버라면 setenv.sh에서 어떤 프로파일이 지정됐는지 확인합니다. spring.profiles.active가 없거나 dev로 돼 있으면 운영에서 개발 설정이 적용됩니다.
# setenv.sh 가 없으면 확인
ls -la /opt/tomcat/bin/setenv.sh
# JVM 옵션에서 프로파일 확인
grep -E "profiles|JAVA_OPTS" /opt/tomcat/bin/setenv.sh
# 실행 중인 프로세스에서 직접 확인
ps aux | grep "[Dd]spring.profiles.active"
export JAVA_OPTS="-server -Xms512m -Xmx2048m -Dspring.profiles.active=prod"
cat /opt/tomcat/bin/setenv.sh- setenv.sh 에 -Dspring.profiles.active=prod 가 포함돼 있는가
- JAVA_OPTS 에 DB 비밀번호가 직접 하드코딩되지 않고 환경변수 참조(${DB_PASSWORD}) 방식인가
- setenv.sh 파일 권한이 644가 아닌 750 또는 700인가 (다른 계정이 읽으면 안 됨)
- Tomcat 재기동 후 ps aux | grep java 에서 프로파일 값이 의도대로 나타나는가
트러블슈팅
원인: Spring Boot 프로파일이 지정되지 않으면 기본 application.yml만 로드됩니다. 개발 DB 주소가 기본 파일에 있거나, setenv.sh에 -Dspring.profiles.active 설정이 누락된 경우입니다.
# 1. 실행 중인 프로세스의 프로파일 확인
ps aux | grep java | grep profiles.active
# 아무것도 안 나오면 프로파일 미지정 상태
# 2. 어떤 설정 파일이 로드됐는지 애플리케이션 로그에서 확인
grep -E "profiles|config.*loaded|active profile" /opt/tomcat/logs/catalina.out | head -20
# 3. setenv.sh 수정 — 프로파일 지정 추가
vi /opt/tomcat/bin/setenv.sh
# 아래 줄 추가
# export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=prod"
# 4. Tomcat 재기동
systemctl restart tomcat
# 5. 재기동 후 확인
grep "The following profiles are active" /opt/tomcat/logs/catalina.out | tail -3
예방: 배포 체크리스트에 "setenv.sh 프로파일 확인" 항목을 추가하고, 배포 스크립트에서 기동 후 로그로 프로파일을 검증합니다.
원인: Java 애플리케이션은 JVM 기동 시점에 설정을 읽어 메모리에 올립니다. 파일을 수정해도 실행 중인 JVM에는 변경이 반영되지 않습니다.
# 설정 파일 수정 후 현재 적용된 값 확인 (Spring Actuator가 있는 경우)
curl http://localhost:8080/actuator/env | python3 -m json.tool | grep -A3 "datasource.url"
# 파일이 수정됐지만 프로세스는 구 값을 갖고 있음을 확인
# /proc/PID/environ 은 기동 시점 환경변수를 반영 → 변경 없음
# 일부 앱은 HUP 시그널로 재로드 지원 (NGINX 등) — Java 앱은 일반적으로 미지원
# kill -HUP $(pgrep -f myapp.jar) ← 대부분의 Spring 앱에서 동작 안 함
# 올바른 방법: 점검 후 재기동
systemctl stop myapp
# 또는 Tomcat이라면
systemctl stop tomcat
# 설정 변경 확인
grep "datasource.url" /etc/myapp/application-prod.yml
# 재기동
systemctl start myapp
systemctl status myapp
무중단이 필요한 경우: 로드밸런서에서 해당 인스턴스를 제외(Drain) → 재기동 → 헬스체크 통과 후 복귀 순서로 처리합니다.
설정 파일 백업 전략
설정 파일 변경 이력 관리
운영 서버의 설정 파일은 변경 전 반드시 백업합니다. 잘못 수정했을 때 빠르게 복구하기 위해서입니다.
# 수정 전 백업 (날짜 포함)
cp /etc/myapp/application-prod.yml \
/etc/myapp/application-prod.yml.$(date +%Y%m%d_%H%M%S).bak
# 백업 목록 확인
ls -lt /etc/myapp/*.bak | head -5
# 이전 버전으로 복구
cp /etc/myapp/application-prod.yml.20260530_143022.bak \
/etc/myapp/application-prod.yml
# 설정 변경 이력은 git으로도 관리 (비밀값 제외한 템플릿)
# 민감 정보가 없는 application-prod.yml.template 을 저장소에 유지
실제 업무에서 이 지식이 쓰이는 상황:
인프라 엔지니어가 환경 설정 관리에서 가장 자주 마주치는 상황 세 가지입니다.
1. 신규 서버 환경 구성 시:
운영 서버를 새로 세팅할 때 개발자가 만든 application.yml 그대로 올리면 안 됩니다. setenv.sh에 SPRING_PROFILES_ACTIVE=prod를 설정하고, DB 접속 정보는 OS 환경변수로 주입하는 것이 기본 체크리스트입니다.
2. 설정 변경 요청 처리:
# 인프라 엔지니어의 설정 변경 표준 절차
# 1. 현재 설정 백업
cp /opt/tomcat/bin/setenv.sh /opt/tomcat/bin/setenv.sh.$(date +%Y%m%d).bak
# 2. 변경
vi /opt/tomcat/bin/setenv.sh
# 3. 적용을 위한 재기동 (무중단 필요 시 LB에서 먼저 제외)
systemctl restart tomcat
# 4. 적용 확인
sleep 10
curl -s http://localhost:8080/actuator/health | python3 -m json.tool
grep "profiles are active" /opt/tomcat/logs/catalina.out | tail -3
3. 보안 감사 대응: Git 저장소에 비밀번호가 커밋됐는지 점검 요청이 오면 아래를 씁니다.
# 저장소에 .env 파일이 들어간 적 있는지 확인
git log --all --full-history -- .env
git log --all --full-history -- "**/*.properties"
설정 관리를 잘 하는 팀은 배포 사고의 절반을 예방합니다. 다음 모듈에서는 이런 설정 변경 이력을 체계적으로 관리하는 Git 형상관리 실무를 다룹니다.