# [Docker Compose 실습] Spring Boot 컨테이너 설정 맞추기
- Spring Boot 컨테이너 설정 맞추기 ← 현재 문서
Compose 파일을 정리해도 앱 컨테이너 자체가 올바르게 실행되지 않으면 전체 환경은 뜨지 않는다. 이번 실습의 목표는 Spring Boot 앱 이미지가 어떤 JAR을 실행하는지, 어떤 포트를 여는지, DB 주소를 어디서 받는지 맞추는 것이다.
현재 Dockerfile의 핵심 문제
현재 Dockerfile은 JAR 파일명을 환경 변수로 조립해서 실행한다.
ENV PROJECT_NAME=discodeit
ENV PROJECT_VERSION=0.0.1-SNAPSHOT
ENTRYPOINT ["sh", "-c", "java $JVM_OPTS -jar build/libs/${PROJECT_NAME}-${PROJECT_VERSION}.jar"]
하지만 현재 build.gradle에는 다음 값이 있다.
version = '2.0-M9'
settings.gradle의 프로젝트 이름은 다음과 같다.
rootProject.name = 'discodeit'
이 조합이면 기본 JAR 이름은 discodeit-2.0-M9.jar 형태가 될 가능성이 높다. Dockerfile은 discodeit-0.0.1-SNAPSHOT.jar를 실행하려고 하므로, 컨테이너 시작 시 파일을 찾지 못할 수 있다.
가장 단순한 해결책은 빌드된 JAR을 고정된 이름으로 복사해서 실행하는 것이다.
권장 Dockerfile 형태
아래는 현재 프로젝트에 맞춘 정리안이다.
FROM amazoncorretto:17 AS builder
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
RUN chmod +x gradlew
COPY src src
RUN ./gradlew clean bootJar -x test --no-daemon
FROM amazoncorretto:17
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 80
ENV JVM_OPTS=""
ENTRYPOINT ["sh", "-c", "java $JVM_OPTS -jar app.jar"]
이 방식의 핵심은 COPY --from=builder /app/build/libs/*.jar app.jar다. Gradle 버전이 바뀌어 JAR 파일명이 달라져도 최종 이미지 안에서는 항상 app.jar로 실행한다.
또한 빌드 스테이지와 실행 스테이지를 나누면 최종 이미지에 소스 코드와 Gradle 빌드 산출 과정을 덜 남길 수 있다. 이 프로젝트는 학습용이라 치명적인 문제는 아니지만, 컨테이너 이미지를 이해하고 관리하는 관점에서는 분리하는 편이 좋다.
build 대신 bootJar를 쓰는 이유
Spring Boot 앱을 컨테이너에서 실행할 때 필요한 것은 실행 가능한 Boot JAR이다. ./gradlew build는 테스트, 검증, 여러 빌드 작업을 함께 포함한다. 현재 Dockerfile은 -x test로 테스트를 제외하고 있지만, 의도는 "실행 JAR 만들기"에 가깝다.
그래서 Docker 이미지 빌드에서는 다음처럼 목적을 명확히 해도 된다.
./gradlew clean bootJar -x test --no-daemon
테스트를 이미지 빌드 단계에서 제외할지는 팀의 선택이다. 로컬 학습 환경에서는 빌드 속도를 위해 제외할 수 있다. CI에서는 이미지 빌드 전에 별도의 test 단계를 두는 편이 더 명확하다.
컨테이너의 DB URL 맞추기
현재 application-prod.yml에는 다음 설정이 있다.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/discodeit_prod
컨테이너 안에서 이 값이 그대로 쓰이면 앱은 DB 컨테이너를 찾지 못한다. Compose에서 환경 변수로 덮어써야 한다.
app:
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/${POSTGRES_DB}
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
이 값이 들어가면 Spring Boot는 application-prod.yml의 localhost URL 대신 Compose 환경에 맞는 db 서비스 이름을 사용한다.
더 명확하게 하려면 application-prod.yml의 DB URL도 운영 기본값이 아니라 환경 변수 기반으로 바꿀 수 있다.
```yaml
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
```
그러면 운영 프로필은 "이 값은 외부에서 주입되어야 한다"는 의도가 더 선명해진다.
포트 맞추기
현재 운영 프로필은 앱 포트를 80으로 둔다.
server:
port: 80
Dockerfile도 같은 포트를 문서화한다.
EXPOSE 80
Compose는 호스트 8081을 컨테이너 80에 연결한다.
ports:
- "${APP_PORT:-8081}:80"
이 셋이 같은 방향을 보고 있어야 한다.
| 위치 | 값 | 의미 |
|---|---|---|
application-prod.yml | server.port: 80 | Spring Boot가 컨테이너 안에서 여는 포트 |
Dockerfile | EXPOSE 80 | 이미지가 80번 포트를 사용한다는 힌트 |
docker-compose.yml | ${APP_PORT:-8081}:80 | 호스트 포트를 컨테이너 80에 연결 |
이미지 빌드 확인
Dockerfile을 정리한 뒤에는 앱 이미지만 먼저 빌드한다.
docker compose build app
빌드가 성공하면 컨테이너를 실행한다.
docker compose up -d app
다만 app은 db에 의존하므로 실제로는 다음처럼 전체를 띄우는 편이 자연스럽다.
docker compose up -d --build
실패하면 앱 로그를 먼저 본다.
docker compose logs -f app
JAR 파일명 문제라면 로그에 Unable to access jarfile 같은 메시지가 나온다. DB URL 문제라면 Connection refused, UnknownHostException, 인증 실패 메시지가 나온다.
체크리스트
- Dockerfile이 특정 버전 JAR 이름에 의존하지 않는다.
- 최종 실행 명령이
java $JVM_OPTS -jar app.jar처럼 단순하다. - 앱의 컨테이너 내부 포트는
80으로 맞춰져 있다. - Compose의 앱 DB URL은
localhost가 아니라db서비스 이름을 사용한다. - 이미지 빌드 실패와 컨테이너 실행 실패를 로그로 구분할 수 있다.
이 단계까지 끝나면 앱 컨테이너는 "어떤 JAR을 실행할지"와 "어디 DB에 붙을지"가 명확해진다.