개요

저번 팀 프로젝트에서는 application.properties에 spring.profile.include=secret을 적고,
github에 공유하지 않는 application-secret.properties를 통해 비밀값들을 관리했었다.
그리고 배포 과정에서 Github Secrets를 사용해 그 값들을 암호화했다.
그런데 실전 스프링부트 책을 읽으며 이런 방식말고도 환경 변수를 이용하는 방법과
local(로컬), dev(개발), prod(운영)별로 세팅을 다르게 하는 방법을 알 수 있었다.
환경 변수로 비밀을 관리하는 건 어렵지 않으니
환경별 세팅하는 방법에 대해 자세히 알아보자!!
1. Profile을 통해 환경별 설정하기
먼저 Profile을 통해 환경별로 설정하는 걸 배워보자.
Profile 방식: 환경별 설정의 기본
Profile은 정해진 파일명으로 환경별 설정을 구분하는 Spring Boot의 기본 방법이다.
하나의 파일 안에서 ---을 통해 환경을 구분할 수도 있고, 여러 파일로 환경을 구분할 수도 있다.
기본 Profile 사용법
# application.yml
spring:
profiles:
active: ${ENVIRONMENT:local}
---
spring:
config:
activate:
on-profile: local
datasource:
url: jdbc:h2:mem:testdb
jpa:
show-sql: true
---
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://prod-server:3306/mydb
jpa:
show-sql: false
이런 식으로 하나의 파일에서 ---을 통해 각 프로필을 구분할 수 있다.
제일 위에 있는 spring.profiles.active 에 들어가는 값으로 어떤 환경을 쓸지 결정하는데
${ENVIRONMENT:local}의 의미는 ENVIRONMENT가 있다면 그 값을 쓰지만,
없다면 기본값으로 local을 쓰겠다는 것이다.
그리고 각 환경에는 spring.config.activate.on-profile 에 들어가는 값으로 이름을 지정한다.
이 이름이 위에서 설명한 ${ENVIRONMENT:local}에 들어가는 값이다.
# application.yml
spring:
profiles:
active: ${ENVIRONMENT:local}
# application-local.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
jpa:
show-sql: true
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-server:3306/mydb
jpa:
show-sql: false
이런 식으로 여러 파일을 통해 환경을 구분할 수도 있는데
이 경우 파일 이름으로 application-{환경}.yml과 같은 Spring boot 컨벤션을 지켜야 한다.
2. 설정 파일 추가하기 (Profile Include vs Config Import)
다음은 추가적으로 설정 파일을 추가하는 2가지 방법에 대해 알아보자.
1. Profile Include: 기능별 모듈화
spring.profiles.include를 사용하면 기능별로 설정을 분리할 수 있다.
# application.yml
spring:
profiles:
active: ${ENVIRONMENT:local}
include:
- database
- cache
- security
- monitoring
---
# application-database.yml
spring:
config:
activate:
on-profile: database
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
---
# application-cache.yml
spring:
config:
activate:
on-profile: cache
cache:
type: redis
redis:
host: ${REDIS_HOST:localhost}
port: 6379
---
# application-security.yml
spring:
config:
activate:
on-profile: security
security:
cors:
allowed-origins: ${CORS_ORIGINS:*}
jwt:
expiration: 86400000
아래와 같이 하면 환경별 설정 + 기능별 모듈화를 동시에 진행할 수 있다.
# local 환경에서는 모든 기능 + H2 DB
spring:
profiles:
active: local
include: database,cache,security,monitoring
---
spring:
config:
activate:
on-profile: local
datasource:
url: jdbc:h2:mem:testdb # database 프로필 설정을 덮어씀
# prod 환경에서는 모든 기능 + MySQL
---
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://prod-server:3306/mydb # database 프로필 설정을 덮어씀
Config Import 방식: 설정 파일 분리
Config Import는 외부 설정 파일을 가져오는 Spring Boot 2.4+의 기능이다.
# application.yml
spring:
profiles:
active: ${ENVIRONMENT:local}
config:
import:
- classpath:database.yml
- optional:file:./secrets/credentials.yml
# database.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
위와 같이 임의의 파일명으로 설정을 추가할 수 있다.
| 기능 | Profile Include | Config Import |
| 목적 | 기능별 설정 모듈화 | 외부 파일 가져오기 |
| 파일 구조 | application-{기능}.yml | 임의의 파일명 가능 |
| 활성화 | spring.profiles.include | spring.config.import |
| 사용 시점 | 기능별 설정 분리 | 민감정보/팀별 설정 분리 |
즉, 기본적으로는 Profile Include를 쓰고, 외부 파일을 가져올 때 Config Import를 쓴다고 생각해도 좋을 거 같다!
(개인적 생각)
3. 환경별 설정의 핵심: 무엇이 달라야 할까?
환경을 나누는 이유는 개발 편의성과 운영 안정성을 동시에 확보하기 위함이다.
각 환경에서 달라야 하는 핵심 설정들을 살펴보자.
공통 설정(application.yml)
먼저 모든 환경에서 동일한 설정들은 기본 파일에 두어 중복을 제거하자.
# application.yml - 공통 설정
spring:
application:
name: my-project
profiles:
active: ${ENVIRONMENT:local}
include: database,cache,security,monitoring # 기능별 모듈화
jpa:
open-in-view: false
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health,info
기능별 모듈화 (Profile Include 활용)
# application-database.yml
spring:
config:
activate:
on-profile: database
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
---
# application-cache.yml
spring:
config:
activate:
on-profile: cache
cache:
type: redis
redis:
timeout: 2000ms
---
# application-security.yml
spring:
config:
activate:
on-profile: security
security:
cors:
allowed-origins: ${CORS_ORIGINS:*}
jwt:
expiration: 86400000
---
# application-monitoring.yml
spring:
config:
activate:
on-profile: monitoring
management:
endpoint:
health:
show-details: when-authorized
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
3-1. Local 환경: 개발 편의성
Local 환경은 빠른 개발과 디버깅에 최적화되어야 한다.
---
spring:
config:
activate:
on-profile: local
# 1. 데이터베이스: 설치 없이 바로 사용 가능
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
h2:
console:
enabled: true # H2 콘솔로 DB 확인 가능
# 2. JPA: 개발 편의 설정
jpa:
hibernate:
ddl-auto: create-drop # 매번 테이블 새로 생성
show-sql: true # SQL 쿼리 로그 출력
properties:
hibernate:
format_sql: true # SQL 포맷팅
# 3. 로깅: 모든 정보 상세히
logging:
level:
com.example: DEBUG # 내 코드는 모든 로그
org.springframework.web: DEBUG
org.springframework.security: DEBUG
# 4. 보안: 개발 편의성 우선
security:
cors:
allowed-origins: "*" # 모든 도메인 허용
jwt:
secret: local-simple-key # 간단한 키
expiration: 86400000 # 24시간 (길게 설정)
# 5. 외부 서비스: Mock 사용
payment:
api:
url: http://localhost:3001/mock-payment
email:
service:
enabled: false # 실제 이메일 발송 안 함
Local 환경의 특징:
- 설치/설정 최소화: H2처럼 별도 설치 없이 바로 사용
- 풍부한 로그: 개발 중 문제 파악을 위한 상세 로그
- 느슨한 보안: CORS 모든 허용, 간단한 JWT 키
- Mock 서비스: 실제 외부 API 호출 없이 개발
3-2. Dev 환경: 실제 환경과 유사하게
Dev 환경은 실제 운영 환경을 시뮬레이션하면서도 개발팀이 자유롭게 테스트할 수 있어야 한다.
---
spring:
config:
activate:
on-profile: dev
# 1. 데이터베이스: 실제 DB 사용, 하지만 테스트 데이터
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/myproject_dev
username: ${DB_USERNAME:dev_user}
password: ${DB_PASSWORD:dev_pass}
driver-class-name: com.mysql.cj.jdbc.Driver
# 2. JPA: 스키마 자동 업데이트 허용
jpa:
hibernate:
ddl-auto: update # 개발 중 스키마 변경 허용
show-sql: false # 성능을 위해 SQL 로그 비활성화
database-platform: org.hibernate.dialect.MySQL8Dialect
# 3. 로깅: 필요한 정보만
logging:
level:
com.example: INFO # 중요한 정보만
org.springframework: WARN
file:
name: logs/dev-application.log
# 4. 보안: 적당한 수준
security:
cors:
allowed-origins:
- http://localhost:3000
- https://dev-frontend.ourcompany.com
jwt:
secret: ${JWT_SECRET:dev-secret-please-change}
expiration: 28800000 # 8시간
# 5. 외부 서비스: 샌드박스/테스트 환경
payment:
api:
url: https://sandbox.payment-provider.com
key: ${PAYMENT_TEST_KEY}
email:
service:
enabled: true
url: https://test-email.service.com
# 모든 이메일을 개발팀으로 리다이렉트
override-recipient: dev-team@ourcompany.com
# 6. 캐시: 단순한 Redis
spring:
cache:
type: redis
redis:
host: ${REDIS_HOST:localhost}
port: 6379
Dev 환경의 특징:
- 실제 DB 사용: MySQL 등 운영 환경과 동일한 DB
- 테스트 안전성: 실제 서비스에 영향 없는 샌드박스 API
- 적당한 로깅: 디버깅 가능하지만 성능 고려
- 스키마 자동 업데이트: 개발 중 DB 스키마 변경 허용
3-3. Prod 환경: 안정성과 보안 최우선
Prod 환경은 안정성, 보안, 성능이 가장 중요하다..
실수로 문제가 생기면 실제 사용자에게 피해가 가기 때문!
---
spring:
config:
activate:
on-profile: prod
# 1. 데이터베이스: 고가용성과 보안
datasource:
url: jdbc:mysql://${DB_HOST}:3306/myproject_prod
username: ${DB_USERNAME} # 반드시 환경변수로
password: ${DB_PASSWORD} # 반드시 환경변수로
driver-class-name: com.mysql.cj.jdbc.Driver
# 연결 풀 최적화
hikari:
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
# 2. JPA: 스키마 변경 절대 금지
jpa:
hibernate:
ddl-auto: validate # 스키마 변경 시 에러 발생
show-sql: false # 성능과 보안을 위해 비활성화
properties:
hibernate:
jdbc:
batch_size: 50 # 성능 최적화
order_inserts: true
order_updates: true
# 3. 로깅: 최소한의 로그, 보안 고려
logging:
level:
com.example: WARN # 경고 이상만
org.springframework: ERROR
org.hibernate: ERROR
file:
name: /var/log/app/production.log
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
# 4. 보안: 최고 수준
security:
cors:
allowed-origins: ${CORS_ALLOWED_ORIGINS} # 엄격한 도메인 제한
jwt:
secret: ${JWT_SECRET_KEY} # 복잡한 환경변수
expiration: 3600000 # 1시간 (보안을 위해 짧게)
# 5. 외부 서비스: 실제 서비스, 실제 비용!
payment:
api:
url: https://api.payment-provider.com
key: ${PAYMENT_PROD_KEY} # 실제 결제 키
timeout: 10000 # 짧은 타임아웃
retry:
max-attempts: 3
email:
service:
enabled: true
url: https://email.service.com
# 실제 고객에게 발송!
# 6. 캐시: 고가용성 Redis 클러스터
spring:
cache:
type: redis
redis:
cluster:
nodes: ${REDIS_CLUSTER_NODES}
timeout: 1000ms
lettuce:
pool:
max-active: 20
max-idle: 10
# 7. 모니터링: 보안 강화
management:
endpoints:
web:
exposure:
include: health,info # 최소한의 정보만
endpoint:
health:
show-details: never # 내부 정보 노출 금지
security:
enabled: true
server:
port: 9090 # 별도 포트로 분리
# 8. 성능 최적화
server:
compression:
enabled: true # GZIP 압축
spring:
web:
resources:
cache:
period: 31536000 # 1년 캐시
Prod 환경의 특징:
- 절대 안전성: ddl-auto: validate로 스키마 변경 금지
- 최고 보안: 모든 민감 정보를 환경변수로, 최소한의 정보만 노출
- 성능 최적화: 배치 처리, 압축, 캐시 등 모든 최적화 활성화
- 실제 서비스: 실제 고객 데이터, 실제 결제, 실제 이메일
4. 프로필 활성화 방법들
우선순위순으로 나열
1. 명령어 인자로 직접 지정 (자주 사용)
# JAR 실행할 때
java -jar myapp.jar --spring.profiles.active=prod
# Maven 실행할 때
./mvnw spring-boot:run -Dspring.profiles.active=dev
# Gradle 실행할 때
./gradlew bootRun --args='--spring.profiles.active=prod'
2. JVM 시스템 프로퍼티로
java -Dspring.profiles.active=prod -jar myapp.jar
3. 환경변수로
export SPRING_PROFILES_ACTIVE=prod
java -jar myapp.jar
# 또는 한 줄로
SPRING_PROFILES_ACTIVE=prod java -jar myapp.jar
4. application.properties에서 기본값
# application.properties
spring.profiles.active=${ENVIRONMENT:local}
5. 설정 파일 작성 팁
1. 환경변수 활용
# 민감한 정보는 반드시 환경변수로
spring:
datasource:
username: ${DB_USERNAME} # ✅ 좋음
password: ${DB_PASSWORD} # ✅ 좋음
# username: hardcoded_user # ❌ 나쁨
# password: hardcoded_pass # ❌ 절대 안됨
2. 기본값 제공
# 환경변수가 없을 때를 대비한 기본값
spring:
datasource:
url: ${DATABASE_URL:jdbc:h2:mem:testdb} # 기본값: H2
server:
port: ${PORT:8080} # 기본값: 8080
3. 검증 로직 추가
@Component
@Profile("prod")
public class ProductionConfigValidator implements InitializingBean {
@Value("${jwt.secret}")
private String jwtSecret;
@Override
public void afterPropertiesSet() throws Exception {
if ("dev-secret-key".equals(jwtSecret)) {
throw new IllegalStateException(
"운영환경에서 개발용 JWT 키를 사용할 수 없습니다!");
}
}
}
4. README에 명확한 가이드
## 환경별 실행 방법
### 로컬 개발
```bash
./mvnw spring-boot:run
# H2 콘솔: http://localhost:8080/h2-console
결론
환경별 설정 관리는 개발 효율성과 운영 안정성을 모두 잡을 수 있는 중요한 기술이다.
처음에는 간단하게 시작해서, 프로젝트가 복잡해질수록 점진적으로 고도화해나가는 것이 좋을 것 같다.
무엇보다 "실수로 운영 환경에 문제가 생기지 않도록" 하는 것이 가장 핵심이다!!
'Spring Framework > Spring Boot' 카테고리의 다른 글
| [Spring Boot] 커스텀 프로퍼티 완벽 가이드 (2) | 2025.08.28 |
|---|---|
| [Spring Boot] 스프링 부트 이벤트 리스너 구현과 등록 (2) | 2025.07.17 |
| [Spring Boot] SpringApplication.run() 메서드 인자에 관해서 (0) | 2025.07.17 |
| [Spring Boot] 리액티브 프로그래밍이란? (0) | 2025.07.17 |
| [Spring Boot] 스프링 부트 애플리케이션 종료 (1) | 2025.07.02 |