Squad 프로젝트를 진행하면서 처음으로 Docker와 RDS를 사용하게 되었습니다. 개발 환경에서는 문제없이 동작하던 애플리케이션을 운영 환경에 배포하려고 하니, 환경변수, 프로필, 설정 파일의 관계가 명확하지 않아 어려움을 겪었습니다.
이 글에서는 제가 겪었던 시행착오를 바탕으로, Docker Compose와 Spring Boot가 어떻게 연결되는지 정리해보겠습니다.
.env.prod → docker-compose → container env → Spring Boot → application-prod.yml
이전에 진행했던 프로젝트에서는 배포 방식이 단순했습니다.
개발할 때:
# application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=admin
spring.datasource.password=1234
배포할 때:
1. application.properties 운영 서버 cfg에 따로 존재
2. .env로 관리하지 않고 properties에 모든 내용이 작성
이 방식의 문제점:
그런데 Docker를 사용하면서 갑자기:
.env.prod 파일docker-compose.ymlapplication-prod.yml${VARIABLE_NAME}이런 개념들이 한꺼번에 등장했습니다.
"왜 이렇게 복잡해진 거지?"
처음에는 이런 생각이 들었습니다. 기존에는 파일 하나만 수정하면 됐는데, 이제는 여러 파일과 환경변수를 관리해야 한다니 오히려 더 복잡해 보였습니다.
하지만 이해하고 나니, 이 방식이 훨씬 더 안전하고 편리하다는 것을 알게 되었습니다:
SPRING_PROFILES_ACTIVE만 바꾸면 설정이 자동으로 전환.env.prod는 .gitignore에 추가하면 절대 커밋되지 않음하지만 이 새로운 방식을 처음 접했을 때 가장 혼란스러웠던 부분은 "어디서 어떤 값을 읽어오는가"였습니다. .env.prod 파일을 만들고, docker-compose.yml에 env_file을 지정했지만, 이 값들이 Spring Boot에서 어떻게 사용되는지 명확하지 않았습니다.
Docker Compose에서 env_file을 지정하면:
services:
app:
...
env_file:
- .env.prod
environment:
SPRING_PROFILES_ACTIVE: prod
Docker는 해당 파일의 모든 KEY=VALUE 쌍을 읽어 컨테이너의 환경변수로 등록합니다.
예를 들어 .env.prod 파일이 다음과 같다면:
SPRING_DATASOURCE_URL=jdbc:postgresql://my-rds:5432/squad
SPRING_DATASOURCE_USERNAME=admin
SPRING_DATASOURCE_PASSWORD=123123
APPLE_BUNDLE_ID=xxx.xxxx.xxxxxx
JWT_SECRET=xxxx
Docker는 파일 내용을 읽어 컨테이너 환경변수로 설정합니다. 이때 중요한 점은 Docker는 이 값들이 어디서 사용될지 알지 못한다는 것입니다. 단순히 환경변수를 설정하는 역할만 수행합니다.
Docker Compose의 역할은 명확합니다. .env.prod 파일의 내용을 읽어 컨테이너 환경변수로 설정하는 것이 전부입니다. Spring Boot가 어떤 값을 필요로 하는지, 어떻게 사용할지는 관여하지 않습니다.
그렇다면 어떤 환경변수가 필요한지는 누가 결정할까요? 답은 Spring Boot입니다.
Spring Boot는 설정 파일에서 ${VARIABLE_NAME} 형태의 플레이스홀더를 발견하면, 런타임에 환경변수에서 해당 값을 읽어옵니다.
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
apple:
bundle-id: ${APPLE_BUNDLE_ID}
위 설정을 보고 Spring Boot는 SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME 등의 환경변수가 필요하다는 것을 인지하고, 컨테이너 환경변수에서 값을 가져옵니다.
여기서 잠깐, "프로필"이라는 용어가 생소할 수 있습니다. 프로필은 쉽게 말해 "실행 환경"을 의미합니다.
같은 애플리케이션 코드를 여러 환경에서 실행할 때, 환경마다 설정이 달라야 합니다. 예를 들어:
코드는 동일하지만, DB 주소나 API 키 같은 설정만 환경에 따라 달라지는 것이죠.
# application-local.yml (로컬 개발용)
spring:
datasource:
url: jdbc:postgresql://localhost:5432/squad
username: admin
password: 1234
apple:
bundle-id: com.myapp.squad.dev
# application-prod.yml (운영 서버용)
spring:
datasource:
url: ${SPRING_DATASOURCE_URL} # .env.prod에서 가져옴
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
apple:
bundle-id: ${APPLE_BUNDLE_ID}
실행할 때 어떤 프로필을 사용할지 지정하면:
SPRING_PROFILES_ACTIVE=local → application-local.yml 사용SPRING_PROFILES_ACTIVE=prod → application-prod.yml 사용프로필 덕분에 하나의 코드베이스로 여러 환경을 유연하게 관리할 수 있습니다.
기존 방식 (properties 직접 수정):
개발 완료 → properties 파일 수정 → 빌드 → 배포
↑ 내용이 properties에 그대로 작성
새로운 방식 (프로필 + 환경변수):
개발 완료 → 빌드 (코드는 그대로!) → 배포 시 SPRING_PROFILES_ACTIVE=prod만 지정
↑ .env.prod는 서버에만 존재, Git에는 없음
코드와 설정이 완전히 분리되어, 같은 JAR 파일로 여러 환경에 배포할 수 있게 된 것입니다.
Spring Boot가 application-prod.yml 파일을 자동으로 읽는 원리는 간단합니다.
environment:
SPRING_PROFILES_ACTIVE: prod
위와 같이 SPRING_PROFILES_ACTIVE 환경변수를 설정하면, Spring Boot는 다음 순서로 설정 파일을 로딩합니다:
application.yml (기본 설정)application-prod.yml (프로필별 설정)이는 Spring Boot의 Profile 기반 설정 메커니즘에 따른 것으로, application-{profile}.yml 네이밍 규칙을 따르면 자동으로 로딩됩니다.
전체 과정을 정리하면 다음과 같습니다:
[1] .env.prod
실제 환경변수 값 정의 (Git에는 커밋하지 않음!)
↓
[2] docker-compose.prod.yml
env_file로 .env.prod 로딩
SPRING_PROFILES_ACTIVE=prod 설정
↓
[3] Docker Container
모든 환경변수가 컨테이너에 등록됨
↓
[4] Spring Boot
SPRING_PROFILES_ACTIVE=prod 확인
→ application-prod.yml 자동 로딩
→ ${VARIABLE_NAME} 형태의 값을 환경변수에서 조회
IntelliJ에서 애플리케이션을 실행할 때는 Docker가 개입하지 않으므로, 환경변수를 별도로 설정해야 합니다.
application.yml만 로딩됩니다.Run Configuration에서 SPRING_PROFILES_ACTIVE=prod를 설정하면:
application-prod.yml이 로딩됩니다.SPRING_DATASOURCE_URL=...
SPRING_DATASOURCE_USERNAME=...
APPLE_BUNDLE_ID=...
JWT_SECRET=...
Docker처럼 .env 파일이 자동으로 로딩되지 않으므로, 로컬에서 운영 환경을 재현하려면 환경변수를 수동으로 관리해야 합니다.
| 구성 요소 | 역할 |
|---|---|
.env.prod | 환경변수 값을 저장하는 파일 (Git에 커밋 안 함) |
docker-compose.yml | .env.prod를 읽어 컨테이너 환경변수로 설정 |
| 컨테이너 환경변수 | Spring Boot가 런타임에 참조하는 실제 값 |
SPRING_PROFILES_ACTIVE | 로딩할 프로필 설정 파일 결정 |
application-prod.yml | 프로필별 추가 설정 정의 |
Docker와 Spring Boot의 역할을 명확히 구분하면, 환경변수와 설정 파일의 관계를 쉽게 이해할 수 있습니다.
처음에는 기존의 단순한 방식보다 복잡해 보였지만, 이 구조 덕분에:
Squad(스쿼드) 프로젝트를 배포하면서 겪었던 시행착오가 좋은 학습 경험이 되었습니다. 다음에는 처음부터 이런 구조로 프로젝트를 설계할 수 있을 것 같습니다.