COPY --from=build

2025. 12. 30. 20:51Docker

COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib:

이 커맨드는 해당 Dockerfile에서 효율성최적화를 담당하는 핵심적인 부분입니다. 단순히 파일을 복사하는 것을 넘어, Docker의 레이어 구조와 Spring Boot의 배포 구조를 깊이 있게 활용하는 설정입니다.

1. 커맨드 구성 요소

  • COPY: 호스트(컴퓨터)나 이전 빌드 스테이지의 파일을 현재 이미지의 파일 시스템으로 복사하는 명령어입니다.
  • --from=build: 이 옵션이 가장 중요합니다. 앞서 #stage 1에서 as build라고 명명했던 스테이지의 결과물로부터 파일을 가져오겠다는 의미입니다. 즉, 현재 스테이지가 아닌 이전 스테이지의 내부 저장소를 뒤지겠다는 뜻입니다.
  • ${DEPENDENCY}/BOOT-INF/lib: 복사할 원본(Source) 경로입니다.
  • ${DEPENDENCY}는 앞에서 /target/dependency로 정의된 변수입니다.
  • Spring Boot JAR 파일의 압축을 풀면, 모든 외부 라이브러리(.jar 파일들)는 BOOT-INF/lib 폴더 안에 위치하게 됩니다.
  • /app/lib: 현재 이미진 내부의 대상(Destination) 경로입니다. 추출한 외부 라이브러리들을 새 이미지의 /app/lib 폴더에 차곡차곡 쌓아두겠다는 의미입니다.

2. 왜 이렇게 복사하는가? (핵심 기술적 이유)

이 명령어는 Docker의 레이어 캐싱(Layer Caching) 전략을 극대화하기 위해 설계되었습니다.

A. 변경 빈도에 따른 계층 분리

일반적인 자바 프로젝트에서 외부 라이브러리(lib)는 한 번 정해지면 거의 바뀌지 않습니다. 반면, 여러분이 작성한 소스 코드(classes)는 매우 자주 바뀝니다.

  • 만약 전체 JAR 파일을 통째로 복사하면, 코드 한 줄만 바꿔도 수백 메가바이트(MB)에 달하는 JAR 전체를 다시 복사하고 레이어를 새로 만들어야 합니다.
  • 하지만 이렇게 lib만 따로 분리해두면, 코드가 바뀌어도 이 COPY ... /app/lib 레이어는 "수정 사항 없음"으로 판단되어 캐시를 사용하게 됩니다. 결과적으로 빌드 속도가 수 초 내로 끝날 만큼 빨라집니다.

B. JAR 파일의 구조 활용

Spring Boot는 실행 시 내부 라이브러리를 로드하기 위해 복잡한 과정을 거칩니다. 이 Dockerfile은 실행 단계에서 아예 라이브러리를 밖으로 다 꺼내 놓음으로써, 자바 가속기(Class Loader)가 압축을 풀 필요 없이 바로 라이브러리들을 읽을 수 있게 해줍니다.

3. 기술적 동작 과정 (Flow)

  1. Stage 1에서 jar -xf를 통해 app.jar의 압축을 풉니다.
  2. 이 과정에서 BOOT-INF/lib 폴더 안에 수십 개의 라이브러리 파일이 생깁니다.
  3. Stage 2에서 이 커맨드가 실행되면, Stage 1의 무거운 파일들(빌드 도구 등)은 무시하고 오직 lib 폴더 안의 파일들만 쏙 골라서 최종 이미지로 가져옵니다.
  4. 결과적으로 최종 이미지에는 빌드에 필요한 찌꺼기는 남지 않고, 실행에 꼭 필요한 라이브러리만 깔끔하게 정리된 상태로 포함됩니다.

요약하자면

이 커맨드는 "이전 단계에서 압축을 풀어둔 외부 라이브러리들만 골라서 현재 이미지의 /app/lib으로 옮겨라. 단, 캐시를 활용할 수 있게 독립적인 레이어로 만들어라"라는 고도로 최적화된 명령입니다.

이 명령 이후에 오는 BOOT-INF/classes 복사 과정과 비교해 보면, 왜 "무거운 것(lib)"을 먼저 복사하고 "자주 변하는 것(classes)"을 나중에 복사하는지 그 전략적 차이를 명확히 알 수 있습니다.

 

 

COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF:

이 커맨드는 Java 실행 환경에서 매우 중요한 역할을 하는 메타데이터(설정 및 정보 파일)를 관리하는 부분입니다. BOOT-INF/lib(라이브러리)와 BOOT-INF/classes(작성한 코드) 사이에서 교량 역할을 하는 데이터들을 옮기는 작업입니다.

1. 커맨드 구성 요소

  • COPY --from=build: 이전 스테이지인 build 단계의 파일 시스템으로부터 데이터를 가져오겠다는 선언입니다.
  • ${DEPENDENCY}/META-INF: 복사할 원본 경로입니다.
  • Spring Boot JAR 파일을 압축 해제하면 루트(Root) 레벨에 META-INF 폴더가 생성됩니다.
  • 이 안에는 애플리케이션의 버전, 빌드 정보, 그리고 가장 중요한 매니페스트(MANIFEST.MF) 파일이 들어 있습니다.
  • /app/META-INF: 최종 이미지 내에서 파일이 저장될 목적지입니다.
  • 나중에 java -cp 명령어로 실행할 때 Java가 이 경로를 참조하여 애플리케이션의 기본 정보를 읽게 됩니다.

2. META-INF 폴더 안에는 무엇이 들어있는가?

Java 애플리케이션에서 이 폴더는 "신분증"과 같은 역할을 합니다. 주요 포함 내용은 다음과 같습니다.

  1. MANIFEST.MF:
  • 메인 클래스의 위치 (Main-Class)
  • 빌드 도구 버전 (Maven/Gradle 버전)
  • Java 버전 및 서명 정보
  1. spring-boot-loader 관련 정보: Spring Boot가 어떤 순서로 라이브러리를 읽어야 하는지에 대한 정보가 포함될 수 있습니다.
  2. 서비스 등록 파일: 특정 라이브러리들이 자동으로 로드되어야 할 때 사용하는 services/ 폴더 등이 포함됩니다.

3. 왜 라이브러리(lib)와 코드(classes) 사이에서 따로 복사하는가?

Docker의 레이어링(Layering) 전략 때문입니다. 이 Dockerfile의 복사 순서를 다시 보면 다음과 같습니다.

  1. lib (가장 안 바뀜): 수백 개의 외부 라이브러리. (제일 아래 레이어)
  2. META-INF (거의 안 바뀜): 앱의 설정 및 버전 정보. (중간 레이어)
  3. classes (가장 자주 바뀜): 우리가 매일 수정하는 소스 코드. (최상단 레이어)

기술적 효과:
만약 여러분이 소스 코드만 수정하고 다시 빌드한다면, Docker는 1번(lib)과 2번(META-INF) 레이어에 대해서는 "이전 빌드와 똑같네? 다시 복사 안 하고 저장된 거(Cache) 그대로 쓸게"라고 판단합니다. 덕분에 빌드 시간이 획기적으로 단축됩니다.

4. 실행 시점에서의 역할

최종 단계의 ENTRYPOINT를 보면 다음과 같은 부분이 있습니다.
"java", "-cp", "app:app/lib/*", ...

여기서 app 폴더(우리가 파일들을 복사해 넣은 곳)가 클래스패스에 포함되어 있습니다. Java 런타임은 실행 시 /app/META-INF/MANIFEST.MF를 읽어 애플리케이션의 설정 상태를 확인하고 구동하게 됩니다.

요약하자면

이 커맨드는 "애플리케이션의 신분증과 설정 정보가 담긴 META-INF 폴더를 캐싱 효율을 위해 독립적인 레이어로 분리하여 복사하라"는 의미입니다. 이를 통해 전체 빌드 시스템의 속도와 구조적 무결성을 동시에 챙길 수 있습니다.

 

 

COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app:

이 명령어는 전체 Docker 빌드 과정의 '화룡점정'이자, 레이어 캐싱 전략의 핵심입니다. 이전 단계에서 라이브러리와 설정을 복사했다면, 여기서는 여러분이 직접 작성한 실제 비즈니스 로직(컴파일된 클래스 파일)을 가져옵니다.

1. 커맨드 구성 요소

  • COPY --from=build: 이전 build 스테이지의 파일 시스템에서 데이터를 가져옵니다.
  • ${DEPENDENCY}/BOOT-INF/classes: 복사할 원본(Source) 경로입니다.
  • Spring Boot 구조에서 BOOT-INF/classes 폴더에는 프로젝트의 src/main/javasrc/main/resources에 있던 내용들이 컴파일되어 들어 있습니다.
  • 즉, 컨트롤러, 서비스, 엔티티 클래스application.yml 같은 설정 파일들이 여기에 해당합니다.
  • /app: 최종 이미지 내의 대상(Destination) 경로입니다.
  • 앞선 단계들에서 /app/lib, /app/META-INF를 만들었으므로, 이 명령어가 실행되면 /app 폴더 아래에 패키지 구조(예: com/optimagrowth/...)가 그대로 복사됩니다.

2. 왜 이 명령어를 가장 마지막에 배치했을까? (최적화의 핵심)

Docker는 위에서부터 아래로 명령어를 실행하며 각 단계를 레이어(Layer)로 저장합니다. 만약 이전 레이어와 파일 내용이 하나도 변하지 않았다면, Docker는 빌드 과정을 생략하고 캐시(Cache)를 사용합니다.

  • 변경 빈도: lib(외부 라이브러리)는 거의 변하지 않지만, classes(코드)는 개발자가 코드를 수정할 때마다 매번 바뀝니다.
  • 캐시 활용: 만약 libclasses를 한꺼번에 복사했다면, 코드 한 줄만 고쳐도 수백 메가바이트의 라이브러리 전체를 다시 복사해야 합니다.
  • 전략적 배치: 하지만 이 Dockerfile처럼 가장 자주 변하는 classes를 가장 마지막에 복사하면, 코드 수정 시 앞선 libMETA-INF 레이어는 그대로 캐시를 사용하여 순식간에 지나가고, 오직 몇 킬로바이트의 클래스 파일 레이어만 새로 생성됩니다.

결과적으로 빌드 시간이 수 분에서 수 초로 단축됩니다.

3. 최종 실행(ENTRYPOINT)과의 연결 고리

마지막 줄의 실행 명령어를 다시 보겠습니다.
ENTRYPOINT ["java","-cp","app:app/lib/*", ...]

  • -cp app: 여기서 지정한 app이 바로 이 COPY 명령어로 만든 디렉토리입니다.
  • Java는 /app 폴더 내부를 뒤져서 우리가 작성한 클래스 파일들을 찾아내고 실행합니다.
  • JAR 파일을 통째로 실행하는 것이 아니라, 이렇게 언팩(Unpacked)된 상태로 실행하면 클래스 로딩 속도가 빨라지고 메모리 효율성이 좋아집니다.

4. 정리: 최종적인 /app 폴더의 구조

이 명령어까지 모두 완료되면 컨테이너 내부의 /app 폴더는 다음과 같은 완벽한 실행 환경을 갖추게 됩니다.

경로 내용 변경 빈도
/app/lib/ 외부 라이브러리 (.jar 파일들) 낮음 (캐시 활용 높음)
/app/META-INF/ 앱 설정 및 매니페스트 정보 낮음
/app/com/... 여러분이 작성한 컴파일된 클래스들 매우 높음 (항상 새로 빌드)

결론

이 커맨드는 "자주 바뀌는 실제 소스 코드만 따로 떼어내어 마지막 레이어에 배치함으로써 빌드 성능을 극대화하라"는 의도를 담고 있습니다. 현대적인 마이크로서비스(MSA) 환경에서 CI/CD 배포 속도를 높이기 위한 정석적인 방법입니다.

'Docker' 카테고리의 다른 글

도커 이미지 레이어는 diff만 저장한다.  (0) 2025.12.30
Docker Layer  (0) 2025.12.30
Multi-Stage Build  (0) 2025.12.30
CMD vs ENTRYPOINT  (0) 2025.12.30
Docker Hub에서 Docker Image 삭제 방법  (0) 2024.12.06