배포 파이프라인의 속도와 이미지 크기를 고려한 4단계 Docker 이미지 최적화 전략을 적용
Jenkins CI/CD 파이프라인으로 단계별 수치 자동 측정
![]() |
![]() |
![]() |
|---|---|---|
| 서가영 @caminobelllo |
박성준 @Sungjun24s |
이채유 @chaeyuuu |
docker-optimization-project/
├── Dockerfile # 최종 최적화 이미지 (Step 4: 레이어 캐시 최적화)
├── docker/
│ ├── dockerfile.naive # 최적화 전 베이스라인
│ ├── dockerfile.step01 # Step 1: 베이스 이미지 교체
│ └── dockerfile.step03 # Step 2: 멀티 스테이지 적용
├── .dockerignore # 빌드 컨텍스트 필터링
└── src/
두 가지 측면에서 최적화를 진행하였습니다.
1. 이미지 용량
2. 속도
빌드 도구, 소스코드, JDK가 전부 최종 이미지에 포함된 상태
FROM eclipse-temurin:17-jdk
WORKDIR /app
COPY . .
RUN ./gradlew build -x test
CMD ["java", "-jar", "build/libs/docker-optimization-0.0.1-SNAPSHOT.jar"]
기존 jdk를 Alpine 기반으로 교체해 이미지 크기 감소
# Before
FROM eclipse-temurin:17-jdk # Ubuntu 기반
# After
FROM eclipse-temurin:17-jdk-alpine # Alpine 기반
| Before | After | |
|---|---|---|
| 이미지 크기 | 832MB | 746B |
빌드 환경과 실행 환경을 분리하여 최종 이미지에 JAR 파일만 포함
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY . .
COPY application.yml src/main/resources/application.yml
RUN chmod +x ./gradlew && ./gradlew build -x test
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
최종 이미지에 Gradle, JDK, 소스코드 미포함
| Before | After | |
|---|---|---|
| 이미지 크기 | 832MB | 247MB |
docker build실행 시 불필요한 파일을 빌드 컨텍스트에서 제외.dockerignore없이COPY . .사용 시.git/·build/등 전체 전송
.git/
build/
.idea/
*.md
Dockerfile*
docker-compose*
Jenkinsfile
- Docker는 레이어 변경 시 이하 레이어를 전부 재실행
- 변경 빈도가 낮은 의존성을 위쪽, 소스코드를 아래쪽 레이어에 배치
# Before: 소스 변경 시 의존성 전체 재다운로드
COPY . .
RUN ./gradlew build
# After: 의존성 레이어 캐시 유지
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
RUN ./gradlew dependencies --no-daemon
COPY src src
RUN ./gradlew bootJar --no-daemon -x test
chmod +x gradlew
START=$(date +%s)
./gradlew bootJar --no-daemon -x test
END=$(date +%s)
echo "▶ Gradle 빌드 소요 시간: $((END - START))초"빌드 성공 시 build/libs/*.jar 파일 크기 자동 출력
START=$(date +%s)
docker build \
-t ${IMAGE_NAME}:${IMAGE_TAG} \
-t ${IMAGE_NAME}:latest \
-f Dockerfile .
END=$(date +%s)
echo "▶ Docker 빌드 소요 시간: $((END - START))초"
# 이미지 크기 출력
docker images ${IMAGE_NAME}:${IMAGE_TAG} \
--format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}'# Jenkins Credentials에 저장된 Docker Hub 계정으로 로그인
echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
docker push ${IMAGE_NAME}:${IMAGE_TAG}
docker push ${IMAGE_NAME}:latest# 기존 컨테이너 중지 후 새 버전으로 재실행
docker stop ${CONTAINER_NAME} || true
docker rm ${CONTAINER_NAME} || true
docker run -d \
--name ${CONTAINER_NAME} \
--restart unless-stopped \
-p ${APP_PORT}:8080 \
-e DB_HOST=172.17.0.1 \
-e DB_USERNAME=${DB_USER} \
-e DB_PASSWORD=${DB_PASS} \
${IMAGE_NAME}:${IMAGE_TAG}DB 계정 정보는 Jenkins Credentials에 저장 후 주입 (
db-username,db-password)
Jenkins 파이프라인에서 자동 측정한 실제 수치
| 단계 | 이미지 크기 | 재빌드 시간 | 개선 포인트 |
|---|---|---|---|
| Naive | 832MB | 2m 39s | 베이스라인 |
| 베이스 이미지 교체 | 746MB | 1m 36s | 크기 감소 |
| 멀티스테이지 빌드 | 247MB | 1m 33s | 크기 감소 |
| .dockerignore | 247MB | 1m 32s (특이사항 : docker push 시간 : 20s) | 컨텍스트 정리 |
| 레이어 캐시 최적화 | 247MB | 3s | 재빌드 속도 |
| Naive | 최종 최적화 | 개선율 | |
|---|---|---|---|
| 이미지 크기 | 832MB | 247MB | 70.3% ↓ |
| 소스 변경 시 재빌드 | 2m 39s | 3s | 98.1% ↓ |
cp: cannot create regular file './application.yml': Permission denied
원인
이전 빌드에서 root 권한으로 application.yml 이 생성된 후 jenkins 유저가 덮어쓸 수 없는 상태
Jenkins 워크스페이스 파일 소유권
application.yml → root 소유 (이전 빌드에서 생성)
jenkins 유저 → 쓰기 권한 없음 → cp 명령어 실패
해결
# 1. 워크스페이스 경로 확인
docker exec jenkins ls /var/jenkins_home/workspace/
# 2. 워크스페이스 전체 권한 변경
docker exec -u root jenkins chmod -R 777 /var/jenkins_home/workspace/docker-optimization/
# 3. Jenkins에서 다시 빌드 실행해결
# 도커 소켓 마운트를 통한 해결
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins:lts해결
# Jenkins 컨테이너 안에서 GID의 그룹을 만들고 Jenkins 사용자를 추가
groupadd -g <docker-sock-gid> docker
usermod -aG docker jenkins


