본문으로 바로가기
Docker 기초 6편: 이미지 Layer와 빌드 최적화
LinkedInGitHub
WORKSPACE

EXPLORER

157 POSTS
BLOG
면접질문
해야하는 거
AI 시대, 개발자는 사라지는가?
Python Counter, 빈도수를 쉽게 세는 방법
정렬
1. 투 포인터
DFS와 BFS
알고리즘 논리
코딩 테스트 핵심 알고리즘 정리파이썬 딕셔너리와 코딩 테스트 활용
예시로 살펴보는 AXAX 프로젝트, 문제 정의부터 확산까지AX 시대의 가치 정의와 현장 리딩
MSA
1. Singleton PatternSOLID
Kafka의 핵심 설계 원리Kafka 메시징 시스템의 구성과 동작 방식Kafka 기본 개념과 EC2 Docker 구성
쿠버네티스 입문
Modular Monolith1. MonolithMSA, 서비스 분리와 운영의 원리
Socket이란 무엇인가
네크워크 참조 모델
전체 데이터 구조REST API의 개념과 설계 원칙2. Field/ Parameter / Argument/ this
Actuator 란?EntityManagerJPA 연관관계 매핑JPA 트랜잭션(Transaction)입력값 검증Java의 AOP(Aspect Oriented Programming)Async : AsynchronousBeanJPA(Java Persistence API)Proxy 패턴Spring MVCdocker container1. Spirng 필요컴포넌트 스캔
Spring 컨테이너
application.yamlLombok
이번 강의는 무엇을 노리고 있을까?Spring AI를 배우기 전에 정리할 것23
1. 기술 스택
개발 문서를 읽기 위한 핵심 기술 용어
Docker를 이해하기 위한 운영체제 기초
sigterm
6. 설정과 저장소, 앱을 운영할 수 있는 상태로 만들기5. Service와 Ingress, 요청은 어디로 흐를까4. 직접 실험하는 Kubernetes, Pod 복구부터 롤백까지3. kubectl과 Pod, 상태에서 원인을 찾는 법2. 클러스터는 명령을 어떻게 Pod로 바꿀까1. 쿠버네티스, 원하는 상태와 컨테이너 이미지Calico쿠버네티스 입문, 원하는 상태를 유지하는 시스템
Psql JSONB, 행 잠금, 멱등성Psql 함수,프로시져,트리거
머신러닝 입문딥러닝 학습 기본 개념데이터 시각화기초 통계와 ML 파이프라인 연결분석 자동화와 파이프라인 설계
CNN 아키텍처 발전 과정이미지 세그멘테이션 모델과 핵심 개념객체 탐지 모델과 핵심 개념
딥러닝 데이터셋 엔지니어링
딥러닝 학습 문제 진단과 디버깅
도메인 적응 방법현대 LLM 워크플로의 패턴
Transformer에서 LoRA 적용 대상 정하기LoRA (Low-Rank Adaptation)
LLM 양자화와 QLoRA
분산 학습과 MLOps딥러닝 모델 경량화와 추론 최적화딥러닝 기본 학습 테크닉딥러닝 중급 학습 테크닉
Mixture of Experts(MoE) 핵심 개념멀티모달 파운데이션 모델 핵심 개념State Space Model과 MambaTransformer와 Vision Transformer
Latent Space
1Chunking?
DevOps 기초 1편
실습에서는?Agile 개요, 왜 필요한가?
CI/CD 기초 4편: 배포 전략과 운영CI/CD 기초 3편: Jenkins와 Argo CD를 이용한 GitOps 배포CI/CD 기초 2편: Docker 이미지와 배포 파이프라인CI/CD 기초 1편: 개념과 GitHub Actions
OCI: 컨테이너 이미지와 런타임의 공통 표준Docker 기초 13편: Compose Healthcheck와 실전 구성Docker 기초 12편: Compose 네트워크와 VolumeDocker 기초 11편: Compose 명령어와 환경 변수Docker 기초 10편: Compose 기본 구조와 이미지 빌드Docker 기초 9편: Docker 및 Kubernetes 네트워크Docker 기초 8편: 컨테이너 런타임과 격리Docker 기초 7편: 이미지 Layer와 tar 내부 구조Docker 기초 6편: 이미지 Layer와 빌드 최적화Docker 기초 5편: 컨테이너 기본 명령어와 VolumeDocker 기초 4편: 가상화와 컨테이너 이미지 생명주기Docker 기초 3편: CI/CD 연결과 배포 원칙Docker 기초 2편: Layer, Registry, Volume과 NetworkDocker 기초 1편: Dockerfile, Image와 Container
NginxNginx 로드 밸런싱과 HTTPSNginx 리버스 프록시와 Spring Boot 연결Nginx 기초와 동작 구조
05. Pinia 상태 관리: store 설계와 사용법04. Vue 컴포넌트 설계: props, emit, slot과 생명주기03. Vue Composition API 정리02. Vue 기초 문법 점검: JavaScript, 템플릿01. Vue.js 입문: 핵심 구조와 렌더링
Java 심화 Part 3: 함수형 프로그래밍과 LambdaJava 심화 Part 2: AnnotationJava 심화 Part 1: Reflection
Java 기초 Part 5: Stream APIJava 기초 Part 4: 제네릭Java 기초 Part 3: 제어문Java 기초 Part 2: 주석과 JavadocJava 기초 Part 1: 백엔드 배경과 Java 실행 구조
Java 디버깅 Part 1: 자주 헷갈리는 핵심 개념Java 디버깅 Part 2: VS Code 자동 컴파일과 프로젝트 구조
Java 실행과 JVM Part 3: ClassLoader와 JVM 메모리Java 실행과 JVM Part 2: 메모리와 데이터 흐름Java 실행과 JVM Part 1: Java와 Python 컴파일 비교
Java 객체지향 Part 5: static 메서드와 중첩 클래스Java 객체지향 Part 4: 상속과 인터페이스Java 객체지향 Part 3: 좋은 설계와 OOP 4대 특성Java 객체지향 Part 2: OOP 핵심 문법Java 객체지향 Part 1: 클래스, 객체, 필드와 생성자
Spring 기초 Part 11: Actuator와 애플리케이션 모니터링Spring 기초 Part 10: 비동기 처리와 @AsyncSpring 기초 Part 9: JPA 트랜잭션과 동시성 제어Spring 기초 Part 8: AOP와 공통 관심사 분리Spring 기초 Part 7: Proxy 패턴과 Spring ProxySpring 기초 Part 6: JPA 연관관계 매핑Spring 기초 Part 5: EntityManager와 영속성 컨텍스트Spring 기초 Part 4: JPA, Entity와 RepositorySpring 기초 Part 3: REST API 요청값과 입력값 검증Spring 기초 Part 2: Spring MVC 요청 처리 흐름Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotation
DNS = Domain Name System
Spring Boot, WebSocket, Vue, Docker 로 Raspberry Pi 실시간 모니터링 프로젝트 만들기 - 1편1. Spring Boot 구현
1. GitHub Project 만들기
Python 코드 품질: 디버깅부터 테스트와 자동화까지Python 01. 실행 구조와 실무 기초
sLLM 핵심 기술과 전체 구조제한된 자원에서 sLLM 구축하기
시대 단상에 대한 주저리주저리
WORKSPACE

SEARCH

제목, 카테고리와 태그로 검색하세요.

VERSION CONTROL

SOURCE CONTROL

masterGitHub Pages
저장소 열기
BUILD STATUS

RUN AND DEBUG

게시물은 GitHub Actions에서 검증하고 정적 페이지로 빌드합니다.

Actions 열기
WORKSPACE

MANAGE

홈 열기전체 게시물태그 보기블로그 소개
Docker 기초 6편: 이미지 Layer와 빌드 최적화●
workspace>posts>devops>docker>docker-06-image-build-and-layers.md
DevOps / Docker2026.08.171 min read6 tags

Docker 기초 6편: 이미지 Layer와 빌드 최적화

컨테이너 이미지 Layer, Push와 Pull, 멀티 스테이지 빌드, RUN, 빌드 캐시와 CMD를 정리한다.

컨테이너 이미지 레이어 구조

컨테이너 이미지는 여러 개의 읽기 전용 레이어로 구성된다.

컨테이너를 실행하면 이미지 레이어 위에 데이터를 쓰고 수정할 수 있는 R/W 레이어가 추가된다.

flowchart LR
    subgraph U[ubuntu Image]
        direction BT
        U1[Layer A<br/>ubuntu 기반 파일]
        U2[Layer B<br/>기본 라이브러리]
        U3[Layer C<br/>기본 설정]
        U1 --> U2 --> U3
    end

    subgraph N[nginx Image]
        direction BT
        N1[Layer A<br/>ubuntu 기반 파일]
        N2[Layer B<br/>기본 라이브러리]
        N3[Layer C<br/>기본 설정]
        N4[nginx 설치 Layer]
        N1 --> N2 --> N3 --> N4
    end

    subgraph W[web app Image]
        direction BT
        W1[Layer A<br/>ubuntu 기반 파일]
        W2[Layer B<br/>기본 라이브러리]
        W3[Layer C<br/>기본 설정]
        W4[nginx 설치 Layer]
        W5[web app source Layer]
        W1 --> W2 --> W3 --> W4 --> W5
    end

    subgraph CT[실행된 Container]
        direction BT
        CI[web app Image Layers<br/>읽기 전용]
        RW[R/W Layer<br/>쓰기 가능]
        CI --> RW
    end

    U --> N --> W -->|docker run| CT
# Layer 1: Ubuntu 기반 이미지 사용
FROM ubuntu:22.04

# Layer 2: nginx 설치
RUN apt-get update && \
    apt-get install -y --no-install-recommends nginx

# Layer 3: 웹 애플리케이션 소스 복사
COPY ./src /var/www/html

# 컨테이너 실행 시 nginx 시작
CMD ["nginx", "-g", "daemon off;"]

이미지 레이어 공유

동일한 레이어는 이미지끼리 공유하기 때문에 중복 다운로드와 업로드가 필요하지 않다. (docker는 재탕의 신이라고 할 수 있다.)

flowchart TB
    subgraph SHARED[공통으로 저장된 레이어]
        L1[Layer 1]
        L2[Layer 2]
        L3[Layer 3]
        L1 --> L2 --> L3
    end

    L3 --> A[Image A]
    L3 --> B4[Layer 4] --> B[Image B]

    subgraph LOCAL[로컬에 이미 다운로드된 레이어]
        L5[Layer 5]
        L6[Layer 6]
        L7[Layer 7]
        L5 --> L6 --> L7
    end

    L7 --> C[Image C]
    L7 --> D8[Layer 8<br/>추가 다운로드 필요] --> D[Image D]
    L7 --> E8[Layer 8<br/>이미 존재] --> E[Image E<br/>다운로드 불필요]
  • 이미지 A를 삭제해도 다른 이미지에서 사용하는 Layer 1, 2, 3은 삭제되지 않는다.
  • 이미지 C를 이미 다운로드했다면 공통 Layer 5, 6, 7을 다시 다운로드하지 않는다.
  • 이미지 D에 새로운 Layer 8이 있다면 Layer 8만 추가로 다운로드한다.
  • 이미지 E의 모든 레이어가 로컬에 있다면 추가 다운로드가 필요하지 않다.

Docker가 동일한 레이어를 판별하는 방법

그렇다면 Docker는 어떻게 동일한 레이어인지 판별하는 것일까?

솔직히.. 궁금하지 않은가? 난 궁금한데

Docker는 레이어의 내용을 SHA-256 방식으로 계산한 Digest를 이용해 동일한 레이어인지 판별한다.

flowchart LR
    C[레이어의 파일 변경분] --> T[tar 형식의 Layer Blob]
    T --> H[SHA-256 계산]
    H --> D[Digest<br/>sha256:...]
    D --> L{로컬에 같은<br/>Digest가 있는가?}
    L -->|있음| R[기존 레이어 재사용]
    L -->|없음| P[레이어 Push 또는 Pull]

쉽게 말하면 레이어는 포장된 택배 상자이고, Digest는 상자 내용으로 만든 고유한 디지털 송장이라고 생각하면 편할 것 같다.

Layer  = 파일 변경분을 담은 택배 상자
Digest = 상자 내용으로 만든 고유 송장 번호

Digest가 레이어를 더 작은 단위로 분리하는 것은 아니다. Dockerfile 명령으로 만들어진 파일 시스템 변경분이 하나의 레이어가 되고, 그 레이어 Blob에 Digest가 부여된다.

Dockerfile 명령 실행
→ 파일 시스템 변경분 생성
→ 하나의 Layer로 묶음
→ SHA-256 Digest 계산
→ 저장,공유,중복 확인

따라서 레이어는 Docker가 저장,전송,캐시하는 기본 단위이지만 반드시 크기가 작지는 않다. 하나의 RUN 명령에서 많은 파일을 설치하면 하나의 레이어가 수백 MB가 될 수도 있다.

Registry의 Image Manifest에는 이미지가 사용하는 레이어의 Digest가 순서대로 기록된다. Docker는 Image를 Pull할 때 Manifest의 Digest와 로컬에 저장된 레이어를 비교한다.

Registry의 Layer Digest
        ↓
로컬에 같은 Digest가 있는가?
        ├─ 있음 → Already exists, 다운로드 생략
        └─ 없음 → 해당 레이어 다운로드

이 구조를 통해 다음 작업이 가능하다.

  • 동일한 레이어의 중복 저장 방지
  • 이미 존재하는 레이어의 다운로드 생략
  • 변경된 레이어만 Push 또는 Pull
  • 다운로드한 레이어의 손상 여부 확인

이미지의 RootFS 레이어 식별값은 다음 명령으로 확인할 수 있다.

docker image inspect nginx \
  --format '{{json .RootFS.Layers}}'

docker image inspect의 RootFS.Layers에는 압축을 해제한 레이어 내용의 식별값인 diff_id가 표시된다. Registry Manifest에서 Push와 Pull에 사용하는 압축된 Layer Blob의 Digest와는 값이 다를 수 있지만, 둘 다 콘텐츠를 기반으로 계산한 식별값.

같은 Dockerfile 명령을 사용했다고 해서 항상 같은 레이어가 만들어지는 것은 아니다.

RUN apt-get update

명령이 같아도 실행 시점에 내려받은 패키지나 생성된 파일이 달라지면 레이어 내용과 Digest도 달라진다. 반대로 이미지 이름이 다르더라도 실제 Layer Blob의 Digest가 같다면 해당 레이어를 공유할 수 있다.

레이어의 동일성 판별과 빌드 캐시 판별도 구분해야 한다.

구분주로 확인하는 정보
레이어 저장,Push,PullLayer Blob의 Digest
Dockerfile 빌드 캐시Dockerfile 명령, 부모 상태, 입력 파일과 빌드 설정

즉, Digest는 레이어를 잘게 나누는 도구가 아니라 이미 만들어진 레이어가 같은 내용인지 확인하는 디지털 식별자.

컨테이너 이미지 Push/Pull

기존 이미지에서 변경된 레이어만 Registry에 Push하고, 다른 환경에서도 필요한 레이어만 Pull한다.

flowchart LR
    subgraph BEFORE[1. 수정 전 이미지]
        direction BT
        B1[Bins / Libs Layer]
        A1[App Layer]
        B1 --> A1
    end

    subgraph AFTER[2. 이미지 수정]
        direction BT
        B2[Bins / Libs Layer<br/>기존 레이어]
        A2[App Layer<br/>기존 레이어]
        NB[변경된 Bins / Libs Layer]
        NA[변경된 App Layer]
        B2 --> A2 --> NB --> NA
    end

    R[(3. Registry<br/>이미지 레이어 저장)]

    subgraph ENGINE[4. 다른 Docker Engine]
        direction BT
        EB[Bins / Libs Layer<br/>기존 보유]
        EA[App Layer<br/>기존 보유]
        ENB[변경된 Bins / Libs Layer<br/>Pull]
        ENA[변경된 App Layer<br/>Pull]
        EB --> EA --> ENB --> ENA
    end

    C[5. 변경된 레이어가 추가된<br/>새 이미지로 Container 실행]

    BEFORE -->|이미지 수정| AFTER
    AFTER -->|변경된 레이어만 Push| R
    R -->|없는 레이어만 Pull| ENGINE
    ENGINE -->|docker run| C
기존 이미지 → 이미지 수정 → 변경된 레이어만 Push → Registry
                                                    ↓
컨테이너 실행 ← 새 이미지 구성 ← 없는 레이어만 Pull ← 다른 Docker Engine
  • 변경되지 않은 기존 레이어는 다시 업로드하거나 다운로드하지 않는다.
  • 수정된 App, Bins/Libs 레이어만 Registry로 Push한다.
  • 다른 Docker Engine은 로컬에 없는 레이어만 Pull한다.
  • 기존 레이어와 새 레이어를 결합한 이미지로 컨테이너를 실행한다.

이미지 생성과 Push

공개 이미지를 그대로 사용할 수도 있지만, 필요한 실행 환경이 없다면 Dockerfile로 직접 이미지를 만든 뒤 Registry에 Push한다.

멀티 스테이지 빌드

멀티 스테이지 빌드는 빌드 환경과 실행 환경을 분리한다. (쓸모없는 파일을 굳이 컨테이너에 올릴 필요가 없지 않는가!)

Vue를 에시로 들어보자.

아래와 같이 진행하면 멀---티 스테이지 빌드이다.

  1. Node.js, npm, Vite로 Vue 소스 코드를 빌드해 dist 디렉터리를 만든다.
  2. 실행 단계의 Nginx 이미지에는 dist 디렉터리만 복사한다.
Vue 소스 코드
  ↓ Node.js, npm, Vite로 빌드
dist 디렉터리 생성
  ↓ dist만 복사
Nginx가 정적 파일 제공

장점은 다음과 같다.

  • 최종 이미지 크기 감소
  • 배포와 다운로드 속도 향상
  • 소스 코드와 빌드 도구가 최종 이미지에서 제외되어 공격 표면 감소
  • 빌드 환경과 실행 환경의 명확한 분리

Vue 애플리케이션 예시

# Stage 1: Vue 프로젝트 빌드
FROM node:22-alpine AS builder

WORKDIR /app

# 의존성 파일을 먼저 복사하여 Docker 빌드 캐시 활용
COPY package*.json ./
RUN npm ci

# 소스 코드를 복사하고 배포용 파일 생성
COPY . .
RUN npm run build

# Stage 2: Nginx를 이용한 실제 서비스
FROM nginx:stable-alpine

WORKDIR /usr/share/nginx/html

# Nginx 기본 페이지 제거
RUN rm -rf ./*

# builder 단계에서 생성한 dist만 복사
COPY --from=builder /app/dist .

EXPOSE 80

# Nginx를 포그라운드로 실행
CMD ["nginx", "-g", "daemon off;"]

Dockerfile의 RUN 명령

RUN은 이미지를 빌드하는 과정에서 명령을 실행할 때 사용한다.

RUN apt-get update && \
    apt-get install -y --no-install-recommends nginx && \
    rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app && chmod 755 /app

주요 용도는 다음과 같다.

  • 프로그램과 패키지 설치
  • 파일과 디렉터리 생성
  • 파일 권한 변경
  • 애플리케이션 빌드

RUN으로 파일 시스템에 적용된 변경 사항은 이미지 레이어에 저장된다. 서로 관련된 명령은 하나의 RUN으로 묶으면 불필요한 중간 파일을 같은 레이어에서 제거할 수 있다.

레이어를 너무 많이 만드는 것도 좋지 않다. 최대한 서로 관련된 기능은 최대한 묶는 것을 추천한다.

빌드 캐시

Docker는 이전 빌드 결과를 캐시한다. Dockerfile 명령과 관련 파일이 바뀌지 않았다면 기존 결과를 재사용해 빌드 시간을 줄인다.

# 일반 빌드: 사용할 수 있는 캐시 재사용
docker build -t my-app .

# 캐시 없이 모든 단계를 다시 실행
docker build --no-cache -t my-app .

--no-cache는 이전 빌드 결과를 재사용하지 않고 Dockerfile의 모든 단계를 새로 실행하는 옵션이다.

명령 순서와 캐시 무효화

Dockerfile은 위에서 아래로 처리된다. 특정 단계의 입력이 바뀌어 캐시를 사용할 수 없으면 그 단계와 이후 단계도 다시 실행된다.

FROM ubuntu:22.04

RUN apt-get update && \
    apt-get install -y --no-install-recommends python3 && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY webserver.py .

CMD ["python3", "webserver.py"]

예를 들어 webserver.py만 변경되면 앞의 패키지 설치 단계는 캐시를 재사용하고, COPY webserver.py . 이후 단계만 다시 처리할 수 있다. 이를 활용하려면 자주 바뀌지 않는 명령을 앞쪽에, 자주 바뀌는 소스 코드 복사를 뒤쪽에 배치한다.

여기서 앞뒤 관계는 Dockerfile 명령과 이미지 레이어의 순서를 설명하는 표현이다. 공식적인 "부모,자식 컨테이너" 종류가 따로 있는 것은 아니다.

TAGS#Docker#Image#Layer#Registry#Multi-stage Build#Build Cache
PREVIOUSDocker 기초 5편: 컨테이너 기본 명령어와 VolumeNEXTDocker 기초 7편: 이미지 Layer와 tar 내부 구조
DISCUSSION

COMMENTS

GitHub 계정으로 로그인하여 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions에 공개 저장되며, 작성 내용과 GitHub 프로필 정보가 다른 방문자에게 보일 수 있습니다.

GitHub 로그인 후 댓글 쓰기Discussion 열기
DOCUMENT STRUCTURE

이 문서에는 목차가 없습니다.

DOCUMENT INFO
TYPE
Markdown
DATE
2026.08.17
READ
1 min read
WORDS
0
CATEGORY
DevOps / Docker
RELATED DOCUMENTS
Docker 기초 7편: 이미지 Layer와 tar 내부 구조Docker 기초 2편: Layer, Registry, Volume과 NetworkDocker 기초 1편: Dockerfile, Image와 ContainerOCI: 컨테이너 이미지와 런타임의 공통 표준Docker 기초 13편: Compose Healthcheck와 실전 구성Docker 기초 12편: Compose 네트워크와 Volume
main DevOps / Docker
1 min readUTF-8Markdown
본문 글씨 크기
RECENTLY OPENED1
Docker 기초 6편: 이미지 Layer와 빌드 최적화recently opened