개발 환경에서는 H2, 운영에서는 PostgreSQL — 외부 설정과 프로파일 관리
같은 JAR을 개발과 운영에 그대로 배포해야 한다. 개발에서는 H2 인메모리 DB를, 운영에서는 PostgreSQL을 써야 한다. 코드를 고치지 않고 — 설정만 바꿔서. Spring Boot의 외부 설정(externalized configuration)과 프로파일(profile)이 이것을 가능하게 한다.
이 글은 프로퍼티 소스 계층, @ConfigurationProperties, 멀티 프로파일, 환경 변수 매핑, 그리고 비밀 관리(secrets management) 패턴을 풀어간다. (Spring Boot Reference - External Configuration)
프로퍼티 소스 계층 — 17개의 우선순위
Spring Boot는 여러 소스에서 프로퍼티를 읽고, 우선순위가 높은 것이 낮은 것을 덮어쓴다:
flowchart TD
P1["1. 명령행 인자<br/>--server.port=9090"] --> P2["2. 환경 변수<br/>SERVER_PORT=9090"]
P2 --> P3["3. SPRING_APPLICATION_JSON"]
P3 --> P4["4. 프로파일별 yml<br/>application-prod.yml"]
P4 --> P5["5. 기본 application.yml"]
P5 --> P6["6. @PropertySource"]
P6 --> DEFAULT["7. 기본값<br/>(Spring Boot 자동 구성)"]
| 우선순위 | 소스 | 사용 시기 |
|---|---|---|
| 1 | 명령행 인자 (--key=value) |
테스트, 일시적 오버라이드 |
| 2 | 환경 변수 (KEY=value) |
프로덕션 배포 (Docker, K8s) |
| 3 | SPRING_APPLICATION_JSON |
복잡한 JSON 설정 |
| 4 | 프로파일별 yml (application-{profile}.yml) |
환경별 설정 |
| 5 | 기본 application.yml |
공통 설정 |
| 6 | @PropertySource |
커스텀 프로퍼티 파일 |
프로덕션 배포에서는 환경 변수를 기본으로 한다 — 12-factor app 원칙. Docker, Kubernetes가 환경 변수로 설정을 주입한다. 코드에 환경별 분기를 넣지 않는다.
왜 환경 변수가 yml보다 우선인가? 같은 JAR 빌드 결과물을 dev/staging/prod에 수정 없이 배포하려면 — 환경마다 달라지는 값(DB 비밀번호, API 키)이 코드에 들어 있으면 안 된다. 환경 변수는 OS 컨테이너 수준에서 주입되므로, JAR 빌드와 설정 분리가 완벽히 된다. application-prod.yml에 password: ${DB_PASSWORD}를 쓰면 — yml은 "이 자리에 환경 변수 값을 넣어라"라는 자리표시자(placeholder)만 가지고, 실제 값은 배포 시점에 환경에서 온다.
비유: 같은 레시피(빌드된 JAR)로 요리하지만 — 각 식당(환경)마다 "오늘의 재료"(환경 변수)를 다르게 공급받는다. 레시피를 고칠 필요 없이, 식당마다 재료만 바꾼다.
내부 메커니즘: 환경 변수 SPRING_DATASOURCE_URL이 어떻게 spring.datasource.url 프로퍼티로 매핑되는가? Spring Boot의 Relaxed Binding 규칙이 변환한다: (1) 밑줄(_)을 점(.)으로, (2) 대문자를 소문자로. SPRING_DATASOURCE_URL → spring.datasource.url. 이 규칙 덕분에 환경 변수 이름이 Spring 프로퍼티 키로 자동 매핑된다. 반대 방향으로는 spring.datasource.url 프로퍼티를 환경 변수로 설정할 때 SPRING_DATASOURCE_URL로 적으면 된다.
환경 변수 매핑 규칙
spring.datasource.url → SPRING_DATASOURCE_URL
spring.datasource.password → SPRING_DATASOURCE_PASSWORD
server.port → SERVER_PORT
점(.)은 밑줄(_)로, 소문자는 대문자로 변환된다. 이 규칙으로 환경 변수만으로 모든 프로퍼티를 오버라이드할 수 있다.
@ConfigurationProperties — 타입 안전 바인딩
// Spring Boot 4 / Java 25 — @ConfigurationProperties
@ConfigurationProperties(prefix = "app")
public record AppProperties(
String name,
int maxConnections,
DatabaseConfig database,
List<String> allowedOrigins,
Map<String, String> features
) {
public record DatabaseConfig(String url, String username, String password) {}
}
// application.yml
# app:
# name: my-service
# max-connections: 100
# database:
# url: jdbc:postgresql://db:5432/myapp
# username: appuser
# password: secret
# allowed-origins:
# - https://app.example.com
# - https://admin.example.com
# features:
# audit: enabled
# caching: disabled
@ConfigurationPropertiesrecord는 프로퍼티를 타입 안전하게 바인딩한다.@Value보다 장점: 컴파일 타임 검증, IDE 자동 완성, 중첩 구조,List/Map지원, record 불변성.
@Validated로 프로퍼티 검증
// Spring Boot 4 / Java 25 — 프로퍼티 검증
@ConfigurationProperties(prefix = "app")
@Validated // 프로퍼티 바인딩 시 검증
public record AppProperties(
@NotBlank String name,
@Min(1) @Max(10000) int maxConnections,
@Valid @NotNull DatabaseConfig database
) {
public record DatabaseConfig(
@NotBlank String url,
@NotBlank String username
) {}
}
// app.name이 비어 있으면 애플리케이션 시작 실패
멀티 프로파일 — 한 파일에서 환경 분리
# Spring Boot 4 / Java 25 — application.yml (다중 프로파일)
spring:
application:
name: my-service
profiles:
active: dev # 기본 프로파일
# 공통 설정
server:
port: 8080
logging:
level:
root: INFO
---
# 개발 환경
spring:
config:
activate:
on-profile: dev
spring:
datasource:
url: jdbc:h2:mem:devdb
username: sa
password:
logging:
level:
com.example: DEBUG
---
# 운영 환경
spring:
config:
activate:
on-profile: prod
spring:
datasource:
url: ${DB_URL:jdbc:postgresql://prod-db:5432/myapp}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
logging:
level:
com.example: WARN
root: WARN
---로 YAML 문서를 분리한다.spring.config.activate.on-profile로 각 섹션의 활성 프로파일을 지정. 프로파일 전환:--spring.profiles.active=prod또는SPRING_PROFILES_ACTIVE=prod.
프로파일별 파일 분리
src/main/resources/
├── application.yml # 공통
├── application-dev.yml # 개발
├── application-staging.yml # 스테이징
└── application-prod.yml # 운영
한 파일에 다중 프로파일을 두거나, 프로파일별 파일로 분리하거나 — 팀 선호에 따라 선택. 파일이 크면 분리가 더 관리하기 쉽다.
비밀 관리(Secrets Management) — 비밀번호를 yml에 넣지 마라
# 나쁜 예: 비밀번호가 코드에 하드코딩
spring:
datasource:
password: super-secret-123 # 절대 하지 마라
# 좋은 예: 환경 변수로 주입
spring:
datasource:
password: ${DB_PASSWORD} # 환경 변수에서 읽음
비밀 관리 패턴
| 방식 | 용도 | 예 |
|---|---|---|
| 환경 변수 | 단일 인스턴스 | DB_PASSWORD=secret |
| Vault / AWS Secrets Manager | 마이크로서비스 | Spring Cloud Vault |
| Kubernetes Secrets | K8s 환경 | secretKeyRef |
${VAR:default} |
선택적 기본값 | ${DB_PASSWORD:devpass} |
비밀번호, API 키, 인증서는 절대 yml 파일에 평문으로 넣지 않는다. 환경 변수, Vault, K8s Secrets로 관리한다. Git에 비밀이 커밋되면 보안 사고다.
Spring Boot 4.0 — 프로퍼티 마이그레이션
Spring Boot 버전이 올라가면서 프로퍼티 이름이 변경될 수 있다. Spring Boot 4.0은 properties migrator를 제공한다:
<!-- 마이그레이션 중 임시 의존성 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
properties migrator는 시작 시 이전 프로퍼티 이름을 감지하고, 새 이름으로 자동 마이그레이션하며 경고를 출력한다. 마이그레이션 완료 후 이 의존성을 제거한다. (Spring Boot Reference - Properties Migrator)
실습 — 프로퍼티 계층 시뮬레이션
// Spring Boot 4 / Java 25 — PropertyHierarchyDemo.java
import java.util.*;
public class PropertyHierarchyDemo {
public static void main(String[] args) {
// 우선순위 시뮬레이션 (낮은 것이 나중에 덮어씀)
Map<String, String> config = new HashMap<>();
// 5. 기본 yml
config.put("server.port", "8080");
config.put("db.url", "jdbc:h2:mem:dev");
config.put("log.level", "DEBUG");
// 4. 프로파일 yml (prod)
config.put("db.url", "jdbc:postgresql://prod-db:5432/myapp");
config.put("log.level", "WARN");
// 2. 환경 변수
config.put("server.port", "9090");
// 1. 명령행 인자
config.put("log.level", "ERROR");
System.out.println("최종 설정:");
config.forEach((k, v) -> System.out.println(" " + k + " = " + v));
}
}
java PropertyHierarchyDemo.java
최종 설정:
server.port = 9090
db.url = jdbc:postgresql://prod-db:5432/myapp
log.level = ERROR
확인할 것: 각 소스가 우선순위에 따라 덮어쓴다. 최종 값은 가장 우선순위가 높은 소스에서 결정된다.
Spring Cloud Config — 중앙 집중식 프로퍼티 관리
마이크로서비스 환경에서는 각 서비스가 자체 application.yml을 가지면 설정이 분산된다. Spring Cloud Config로 중앙 저장소(Git, Vault)에서 모든 서비스의 설정을 관리한다:
# Spring Cloud Config Client (각 마이크로서비스)
spring:
config:
import: optional:configserver:http://config-server:8888
cloud:
config:
name: my-service
profile: prod
Spring Cloud Config Server는 Git 저장소에서 프로퍼티를 읽어 각 서비스에 배포한다. 설정 변경 시 서비스 재배포 없이
/actuator/refresh로 런타임 갱신할 수 있다. (Spring Cloud 생태계 — 본 학습서 범위 밖)
요약 — 이 글의 결론
- 프로퍼티 소스 계층으로 환경별 설정을 외부화한다. 명령행 > 환경변수 > 프로파일 yml > 기본 yml. 프로덕션은 환경 변수를 기본으로.
@ConfigurationPropertiesrecord로 타입 안전 바인딩을 한다.@Validated로 시작 시 검증.@Value보다 안전하고 IDE 친화적.- 멀티 프로파일로 환경을 분리한다.
---로 한 파일에 다중 프로파일 또는 파일 분리.spring.profiles.active로 전환. - 비밀번호를 yml에 하드코딩하지 않는다. 환경 변수, Vault, K8s Secrets로 관리.
${VAR:default}로 환경 변수 주입. - Spring Boot 4.0의 properties migrator로 버전 간 프로퍼티 변경을 안전하게 마이그레이션한다.
생각해 볼 문제
- 환경 변수
SPRING_DATASOURCE_URL이application.yml의spring.datasource.url을 덮어쓰는 규칙(relaxed binding)을 설명하라. @ConfigurationPropertiesrecord에@DefaultValue("42")를 붙이면 어떻게 동작하는가?- 여러 프로파일(dev, fast, cloud)을 동시에 활성화할 때 우선순위는?
- Spring Cloud Config Server로 중앙 집중식 프로퍼티 관리를 할 때의 장단점은?
${DB_PASSWORD:#{environment['DB_PASSWORD']}}와 같은 SpEL 기반 프로퍼티 참조는 언제 필요한가?- Spring Boot 4.0의 properties migrator가 마이그레이션을 수행하는 방식은? (renamed vs removed 프로퍼티)
참고
- Spring Boot 4.0 - External Configuration - 접근 2026-07-10
- Spring Boot 4.0 - Application Properties - 접근 2026-07-10
- Spring Boot 4.0 - Profiles - 접근 2026-07-10
- 12-Factor App - Config - 접근 2026-07-10
- Spring Cloud Config - 접근 2026-07-10
- HashiCorp Vault Spring Integration - 접근 2026-07-10
- Spring Boot 4.0과 이전 버전(Spring Boot 3.x)의 가장 큰 차이점은 무엇인가? 마이그레이션 시 주의할 점은?
- 이 글에서 다룬 개념을 실제 프로젝트에 적용할 때 가장 먼저 고려해야 할 것은?
- Spring Framework 7 Reference - 접근 2026-07-10
- Jakarta EE 11 Specification - 접근 2026-07-10
- Spring Boot 4.0 Reference - 접근 2026-07-10
'Develop Artifacts > Spring Boot' 카테고리의 다른 글
| SpringBoot - 10. deployment (0) | 2026.07.12 |
|---|---|
| SpringBoot - 08. actuator (0) | 2026.07.12 |
| SpringBoot - 07. security (0) | 2026.07.12 |
| SpringBoot - 06. testing (0) | 2026.07.12 |
| SpringBoot - 05. spring-data-jpa (0) | 2026.07.12 |
