도커
SpringBoot 도커 배포, Dockerfile + build.sh 패턴 정리
by 이상한나라의개발자
2025. 8. 4.
Dockerfile
"환경"을 설계한다는 것의 의미
- 모든 개발자와 운영자가 같은 환경에서 서비스가 실행되어야
"내 로컬에선 되는데 서버에선 안 돼요" 문제가 사라진다.
- 운영체제, JDK, 패키지, 파일 위치, 환경변수까지
내 서비스에 필요한 모든 것을 한 줄 한 줄 ‘코드’로 문서화
→ Dockerfile은 이 과정을 **명령어(레시피)**로 적는 곳
- 동일한 Dockerfile에서 빌드하면 "언제, 어디서, 누가 빌드해도" 완벽히 똑같은 이미지가 보장
실무에서 중요하게 여기는 포인트
- FROM:
절대로 아무 베이스 이미지나 쓰지 않는다.
공식 이미지(예: eclipse-temurin, openjdk, gradle 등)만 채택
- COPY:
단순히 jar만 복사?
→ 실무에선 static 파일, config, script도 복사하게 되며
→ 이 단계에서 "파일명 오타, 위치 불일치" 실수 자주 발생
- ENTRYPOINT vs CMD:
ENTRYPOINT는 "무조건 실행되는 명령",
CMD는 "기본 옵션"
→ 실무에서 ENTRYPOINT로 고정, CMD는 추가 옵션 줄 때만 씀
- EXPOSE, ENV:
실운영시 포트 노출, 환경변수 전달 미스 주의!
(실제 운영 환경마다 값이 달라질 수 있기 때문)
build.sh가 실무에서 필수인 이유
"자동화"가 아니면, 언젠간 반드시 사람 손에서 실수난다
- 이미지 빌드, 태깅, 푸시, 컨테이너 중지/삭제/재실행, 환경변수 주입 등
한 번에 4~5단계 작업을 매번 터미널에서 직접?
→ 어제와 오늘, 내가, 동료가, QA가 할 때 항상 똑같이 잘 할 수 있을까?
→ 절대 불가능. 반드시 실수한다.
- build.sh 한 방에 "반복/단순/재현" 문제 해결
- 변수 하나로 포트, 버전, 환경값만 바꾸면
→ “빌드 → 푸시 → 배포”가 완벽히 동일하게 반복
- 자동화 = 실수 방지 + 생산성 증가 + 기록(이력)
실무 자동화 스크립트 설계 시 주의점
- set -e 꼭 넣자!
어느 한 단계라도 실패하면 즉시 종료,
→ 실패 상태에서 컨테이너가 재시작되지 않도록!
- 변수/인자 처리
포트, 버전, 프로필, 환경변수 등
→ "반복"을 없애는 대신 "변수"로 관리.
인자가 없으면 디폴트값으로, 있으면 동적으로 대응!
- 중복 빌드/푸시 절대 금지!
빌드/푸시는 한번만!
(실수로 두 번 돌리는 경우 실시간 장애/트래픽 초과 가능)
- docker rm -f로 기존 컨테이너 강제 삭제
→ 같은 이름의 컨테이너가 있으면 에러, 없으면 그냥 넘어감(실무에서 매우 유용!)
실무 배포 흐름의 본질 (도커 관점에서)
- 내가 작성한 코드
→ (jar로 빌드)
→ (Dockerfile로 이미지 빌드)
→ (이미지에 태그 붙임)
→ (Docker Hub에 push, 운영/테스트/QA 서버에서 pull)
→ (기존 컨테이너 중지/삭제)
→ (새 이미지로 컨테이너 run, 환경에 따라 profile/port/secret 등 주입)
- 이 모든 과정을 반복 가능하게 만드는 게 바로 build.sh!
- build.sh 한 번만 실행하면
→ 코드, 환경, 이미지, 실행…
모두 실수 없이 재현
예시
# Dockerfile
# 1. Java 17을 베이스 이미지로 사용
FROM eclipse-temurin:17-jdk-jammy
# 2. 작업 디렉토리 설정
WORKDIR /app
# 3. jar 파일 복사 (빌드 결과 jar)
COPY build/libs/hello-docker-study-0.0.1-SNAPSHOT.jar app.jar
# 4. 컨테이너 시작 시 실행될 명령어 지정
ENTRYPOINT ["java", "-jar", "app.jar"]
- FROM으로 베이스 이미지(운영체제+JDK)를 지정
- WORKDIR로 작업 경로 지정
- jar 파일만 COPY
- ENTRYPOINT로 앱 실행 명령 고정
#!/bin/bash
set -e # 에러 발생 시 스크립트 즉시 종료
PROFILE=${1:-local}
VERSION=${2:-latest}
case "$PROFILE" in
local|sandbox) PORT=8081 ;;
prod1) PORT=8082 ;;
prod2) PORT=8083 ;;
*) echo "Unknown profile: $PROFILE"; exit 1 ;;
esac
DOCKER_USER="anthonyson904"
APP_NAME="hello-docker-spring"
# 1. jar 빌드
./gradlew build -x test
# 2. 도커 빌드 & 푸시
if [ "$VERSION" == "latest" ]; then
docker build -t $DOCKER_USER/$APP_NAME:latest .
docker push $DOCKER_USER/$APP_NAME:latest
else
docker build -t $DOCKER_USER/$APP_NAME:$VERSION .
docker tag $DOCKER_USER/$APP_NAME:$VERSION $DOCKER_USER/$APP_NAME:latest
docker push $DOCKER_USER/$APP_NAME:$VERSION
docker push $DOCKER_USER/$APP_NAME:latest
fi
# 3. 기존 컨테이너 중지 및 삭제
if [ "$(docker ps -q -f name=$APP_NAME)" ]; then
docker rm -f $APP_NAME
fi
# 4. 새 컨테이너 실행 (:latest로)
docker run -d --name $APP_NAME -e SPRING_PROFILES_ACTIVE=$PROFILE -p $PORT:8080 $DOCKER_USER/$APP_NAME:latest
- PROFILE/PORT/VERSION 등 인자로 관리(유연성↑)
- 항상 최신 jar로 빌드
- VERSION이 있으면 태깅/푸시/롤백까지 완벽 대응
- 컨테이너 중지/삭제/재실행 자동
- 환경변수로 Spring Profile 넘겨서 여러 환경에 대응