본문 바로가기

Spring Framework/Spring Boot

[Spring Boot] 환경별 설정 어떻게 해야할까?

개요

 

저번 팀 프로젝트에서는 application.propertiesspring.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

 

 

결론

환경별 설정 관리는 개발 효율성과 운영 안정성을 모두 잡을 수 있는 중요한 기술이다.

 

처음에는 간단하게 시작해서, 프로젝트가 복잡해질수록 점진적으로 고도화해나가는 것이 좋을 것 같다.

무엇보다 "실수로 운영 환경에 문제가 생기지 않도록" 하는 것이 가장 핵심이다!!