Devin.KR

서비스 이미지 빌드

80분 안팎

학습 목표

빌드 문맥과 실행 사용자를 설정합니다.

개념

이미지 안으로 무엇을 넣을지 정합니다

안내판 컨테이너가 실행되려면 JAR와 Java 실행 환경이 필요합니다. 개발용 편집기, Maven 캐시, 개인 환경 파일까지 포함할 필요는 없습니다. 이번 레슨은 이미 테스트한 실행 가능 JAR를 eclipse-temurin:17-jre에 넣고 비루트 사용자로 시작하는 Dockerfile을 완성합니다. 컨테이너 안에서 다시 빌드하지 않으므로 개발 의존성을 이미지에 복사하지 않습니다. 빌드와 실행의 책임을 분리하여 실패 위치를 쉽게 찾습니다.

package를 이미지 빌드 앞에 둡니다

./mvnw package는 컴파일과 기본 단위 테스트를 거쳐 target/notice-board-1.0.0.jar를 만듭니다. 테스트가 실패하면 이미지 빌드로 넘어가지 않습니다. 앞 모듈의 external 태그 HTTP 검사는 기본 단위 실행에서 제외되어 있으며 이번 Docker 검사가 실제 HTTP 경계를 보완합니다. package 성공을 모든 접속 검증의 성공으로 확대하지 않습니다. JAR 내부 구조와 Maven 수명주기의 배경은 더 읽기에서 확인합니다.

빌드 문맥을 작은 디렉터리로 제한합니다

docker build 명령의 마지막 경로는 Dockerfile이 COPY에 사용할 문맥입니다. 검사기는 build-context 폴더에 app.jar, Dockerfile, .dockerignore만 준비하고 교육용 .env fixture를 추가합니다. COPY의 원본은 이 폴더 기준입니다. 저장소 전체를 문맥으로 보내지 않으므로 다른 작성자의 파일이나 운영 설정이 들어가지 않습니다. 대상 JAR가 없으면 Docker 명령을 바꾸기 전에 package 결과와 정확한 파일명을 확인합니다.

Dockerfile을 위에서 아래로 읽습니다

FROM은 실행 기반 이미지, WORKDIR은 이후 경로 기준, RUN은 빌드 중 실행할 명령, COPY는 문맥의 파일을 이미지로 넣는 지시입니다. USER는 뒤 명령과 기본 실행의 사용자 선택이고 ENTRYPOINT는 컨테이너가 시작할 프로그램입니다. EXPOSE는 사용 포트의 설명이며 호스트 포트를 열지 않습니다. 지시가 실행되는 시점이 다르므로 RUN java -jar로 서비스를 빌드 중 띄우는 실수와 ENTRYPOINT를 구분합니다.

JRE 이미지와 빌드 도구를 구분합니다

제공 기반은 eclipse-temurin:17-jre입니다. JAR를 실행하는 환경이며 여기에서 Maven으로 소스를 컴파일하는 구성을 전제로 하지 않습니다. 개발 머신의 JDK 17로 만든 JAR를 복사합니다. 기반 태그는 같은 이름의 내용이 갱신될 수 있으므로 이 Dockerfile만으로 미래의 비트 단위 빌드 동일성을 보장하지 않습니다. 완료된 이미지 자체의 ID를 기록하여 이번 실행의 산출물을 식별하고 기반 갱신은 별도 변경으로 검토합니다.

숫자 UID로 최소 실행 사용자를 고릅니다

USER 10001:10001은 숫자 UID와 GID로 프로세스를 실행합니다. 이름 계정을 추가하지 않아도 숫자 소유권은 적용할 수 있습니다. COPY --chown=10001:10001로 JAR의 소유자를 지정하고 /data 디렉터리도 그 숫자 사용자에게 맡깁니다. 계정 이름을 조회하는 id -un은 별도 이름 정보가 없으면 실패할 수 있으므로 검사에서는 id -u로 숫자를 읽습니다. UID 0이 아니라는 조건과 필요한 경로에 접근할 수 있다는 조건을 모두 확인합니다.

쓰기 권한을 데이터 경로에 모읍니다

JAR는 실행에 읽을 수 있으면 되고 안내판 파일과 요청 로그는 /data에 씁니다. RUN mkdir와 chown은 USER를 바꾸기 전에 빌드 시 수행합니다. 볼륨을 마운트하면 그 볼륨 소유권은 별도로 확인해야 합니다. 검사기는 새로 만든 자기 볼륨에 한해 초기 소유권을 설정하고 앱 프로세스는 계속 10001로 실행합니다. Permission denied가 나오면 앱을 루트로 바꾸기보다 마운트 경로와 숫자 소유자를 대조합니다.

실행 명령의 배열을 확인합니다

ENTRYPOINT의 JSON 배열은 java, -jar, /app/app.jar를 별도 인자로 전달합니다. 셸 문자열로 감싸지 않아 Java가 주 실행 프로세스가 됩니다. 배열 안에 $NOTICE_NAME을 적는다고 셸처럼 자동 치환되지는 않습니다. 이 서비스는 Spring 설정을 통해 환경 변수를 읽으므로 시작 명령에 이름을 붙이지 않습니다. 따옴표 누락이나 마지막 쉼표는 배열 문법 오류의 원인이며 지시 이름보다 실제 인자 경계를 먼저 읽습니다.

비밀 제외는 마지막 삭제로 해결하지 않습니다

파일을 COPY한 뒤 다음 RUN에서 지우면 앞선 이미지 레이어에 내용이 남을 수 있습니다. .dockerignore는 문맥에서 .env와 *.secret을 제외합니다. 이 실습은 필요한 app.jar만 COPY하므로 허용 파일을 좁히는 정책도 함께 적용합니다. 실제 비밀을 fixture로 쓰지 않고 M03_FAKE_SECRET_ONLY라는 가짜 표식을 사용합니다. 검사기는 docker save의 각 레이어를 읽어 그 표식이 없는지 확인하며 최종 디렉터리 목록만으로 결론 내리지 않습니다.

에러를 어느 경계에서 읽을지 정합니다

COPY의 not found 또는 checksum 계산 오류는 문맥 안에 지정 원본이 있는지 먼저 확인합니다. Unable to access jarfile은 실행 컨테이너 안의 JAR 경로와 읽기 권한을 살펴봅니다. UnsupportedClassVersionError는 빌드와 실행 Java 버전을 대조합니다. Dockerfile을 고쳐도 기존 컨테이너는 자동 갱신되지 않으므로 다시 빌드하고 새 이미지 ID를 관측합니다. 태그의 이름이 같은지보다 변경 후 ID와 컨테이너의 Image 값을 확인합니다.

시작 코드의 결함을 작게 고칩니다

이 레슨 starter의 Dockerfile은 USER 0:0으로 남아 있습니다. 다른 경로와 명령은 제공되므로 사용자 선택을 고친 뒤 id -u로 10001을 확인합니다. check.sh는 JAR 테스트 이후 이미지를 빌드하고 비루트 조건에서 실패한 지점을 출력합니다. 검사 기대값을 0으로 바꾸는 것은 권한 목표를 없애는 수정입니다. 사용자 변경 뒤 두 환경의 HTTP와 데이터 기록이 계속 성공해야 최소 권한과 기능을 함께 충족합니다.

작성자가 검토할 범위를 남깁니다

Docker 실행을 못 하는 환경에서는 FROM의 이미지 이름, COPY의 파일 경로, 배열형 ENTRYPOINT, USER 앞뒤의 권한을 눈으로 검토합니다. 그것은 실행 성공 증거를 대신하지 않습니다. 외부 검증 담당자는 bash check.sh를 실행하고 실제 오류와 이미지 ID를 제출합니다. 이 레슨에서는 Dockerfile과 문맥 제한이 왜 필요한지 설명하고, 서재의 id 장에서 UID·GID와 그룹 권한의 배경을 보완합니다.

리뷰 요청에는 어떤 JAR를 복사했는지, 기본 사용자 숫자가 무엇인지, 쓰기 허용 경로가 어디인지 적습니다. 비루트라는 한 단어만으로 모든 접근이 안전하다고 표현하지 않습니다. Docker daemon 접근 권한이나 저장소 비밀 관리까지 해결한 것은 아니며 이후 권한 모듈에서 더 다룹니다. 지금의 완료 조건은 불필요한 파일을 빼고 서비스가 필요한 범위에서 정상 실행되는 이미지를 만드는 것입니다.

각 지시의 실행 시점과 COPY·USER 문법은 공식 Dockerfile 문서에서 확인할 수 있습니다. 본문 명세의 값은 문서 예제가 아니라 개인 실습에서 관측한 값으로 채웁니다.

따라하기

테스트한 JAR 준비

테스트 실패가 없고 지정 JAR가 생성되었는지 확인합니다. package 실패면 이미지 빌드를 진행하지 않습니다.

./mvnw package
mkdir -p build-context
cp target/notice-board-1.0.0.jar build-context/app.jar
cp Dockerfile .dockerignore build-context/

Dockerfile 사용자 수정

Dockerfile의 USER를 10001:10001로 고치고 build-context로 다시 복사한 뒤 빌드합니다. COPY 원본은 build-context/app.jar입니다.

docker build -t notice-m03:practice build-context

실제 UID 확인

개인 Docker 환경에서 숫자 UID가 10001인지 확인합니다. 미실행 명령이라 예상 숫자를 output에 적지 않습니다.

docker run --rm --entrypoint id notice-m03:practice -u

레이어와 기능 검사

non-root UID, secret fixture absent from image layer와 두 환경의 HTTP 항목을 확인합니다. 모두 통과하면 수동 이미지 태그도 제거합니다.

bash check.sh

확인 문제

실습

Dockerfile의 USER를 비루트 10001:10001로 고치고 데이터 쓰기와 비밀 레이어 제외를 확인합니다. 검사 코드와 기대값은 수정하지 않습니다. bash check.sh가 해당 결함을 거부하고 solution은 전체 HTTP·데이터·명세 검사를 통과해야 합니다. 실행 도구가 없는 환경에서는 external 검증 대기로 남깁니다.

시작 코드·테스트 내려받기

실행 명령

bash check.sh

기대 결과

개인 Docker 환경에서 모든 PASS 항목과 RESULT: 0 failure(s). 실제 imageDigest를 기록한 release.json 생성.

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 컨테이너 이미지와 실행 중 컨테이너의 차이를 설명합니다.