본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
11장 : 배포와 운영

재현 가능한 Maven 빌드와 OCI image

10장에서 의존성과 공급망을 점검했다면, 이제 검증한 소스를 누구나 같은 방식으로 배포 단위까지 만들어야 합니다.

Maven Wrapper는 빌드 도구 버전을 저장소에 고정하고 outputTimestamp는 jar 안의 파일 시각을 일정하게 만듭니다. 이 둘을 거친 실행 jar를 Buildpacks에 넘기면 Dockerfile 없이도 기본적으로 non-root로 실행하는 OCI image를 만들 수 있습니다.

다만 jar의 재현성과 image의 재현성은 같은 보장이 아닙니다. builder와 run image가 바뀌면 애플리케이션 jar가 같아도 최종 image digest는 달라질 수 있습니다.


artifact는 같은 소스에서 재현 가능해야 한다

재현 가능한 빌드는 “내 컴퓨터에서 다시 성공했다”가 아니라 같은 커밋과 같은 입력으로 만든 바이트가 같은지를 묻습니다. Wrapper 버전뿐 아니라 JDK, 플러그인, 의존성, 빌드 시각처럼 결과에 들어가는 입력도 함께 고정해야 합니다.


Maven release와 build-image를 구성한다

project.build.outputTimestamp는 ZIP 항목의 시각 차이로 checksum이 달라지는 일을 막습니다. 아래 스크립트는 먼저 테스트를 포함한 Maven 검증을 끝내고, 통과한 프로젝트만 study-log-api:1.0.0 image로 포장합니다.

pom.xml
<properties>
  <java.version>25</java.version>
  <project.build.outputTimestamp>2026-07-13T00:00:00Z</project.build.outputTimestamp>
</properties>
<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>
build-image.sh
#!/usr/bin/env bash
set -euo pipefail
./mvnw -q clean verify
./mvnw spring-boot:build-image \
  -Dspring-boot.build-image.imageName=study-log-api:1.0.0
sha256sum target/study-log-api-1.0.0.jar
docker image inspect study-log-api:1.0.0 \
  --format '{{.Config.User}} {{json .Config.Labels}}'

jar checksum과 image metadata를 확인한다

스크립트가 끝났다는 사실만으로 non-root와 재현성을 증명할 수는 없습니다. 같은 커밋에서 jar를 두 번 만들었을 때 checksum이 같은지 비교하고, image 설정의 사용자 값이 비어 있거나 root, 0이 아닌지 별도로 확인합니다.

bash build-image.sh
Image 확인 결과
두 번 만든 jar checksum은 같아야 하고 image 사용자 값은 비어 있지 않으면서 root가 아니어야 합니다. OCI digest까지 고정하려면 builder와 run image를 태그가 아니라 digest로 선택하고 네트워크에서 내려받은 계층도 같은지 관리해야 합니다.

jar와 image에 서로 다른 증거를 남긴다

jar checksum은 애플리케이션 artifact의 동일성을, image metadata와 digest는 실제 배포 단위의 구성을 설명합니다. 둘을 릴리스 기록에 함께 남겨야 나중에 “같은 코드인데 왜 image가 달라졌는가”를 builder 변경까지 거슬러 추적할 수 있습니다.

다음에는 이 image를 단독 실행하는 대신 PostgreSQL과 함께 올려, 외부 설정과 영속 volume이 컨테이너 재시작을 견디는지 확인합니다.