[Docker] 컨테이너 이미지 크기와 UFS — 구조부터 최적화까지
Docker·OCI 컨테이너의 이미지 크기는 배포 속도·디스크 사용량·CI/CD 효율을 좌우합니다. 이 글에서는 이미지 크기의 구성, Union File System(UFS)의 동작 원리, 그리고 레이어 캐싱·멀티스테이지 빌드 등 실전 최적화 기법까지 한 흐름으로 정리합니다.
🐳 컨테이너 이미지 크기란? #
컨테이너 이미지는 OS 레이어 + 라이브러리/의존성 + 애플리케이션 코드가 합쳐진 패키지입니다.
1Container Image = Base OS Layer + App Dependencies + Application Code| 이미지가 크면 | 이미지가 작으면 |
|---|---|
| 다운로드/배포 시간 증가 | 배포 속도 향상 |
| 디스크 사용량 증가 | CI/CD 효율 상승 |
| 공격 표면(취약점) 증가 | 보안·운영 비용 감소 |
💡 가장 쉬운 첫걸음은 경량 베이스 이미지(Alpine, Distroless,
-slim)를 쓰는 것입니다.
📦 UFS(Union File System)란? #
Union File System은 여러 파일 시스템 레이어를 하나처럼 합쳐 보여주는 파일 시스템으로, 컨테이너 이미지의 계층(Layer) 구조를 관리합니다. 현재 Docker의 기본·권장 드라이버는 overlay2입니다.
1[Read-Only Layers] ← 이미지 레이어(재사용·캐시)
2├─ Base OS
3├─ Dependencies
4└─ Application Code
5[Writable Layer] ← 컨테이너 실행 시 생성
6└─ Container Runtime Changes
7----------------------------
8Merged View → 컨테이너에서 하나의 파일 시스템처럼 보임동작 원리 #
- Read-Only 레이어 — 이미지 빌드 시마다 쌓이며, 동일하면 재사용(캐싱) 됩니다.
- Writable 레이어 — 컨테이너 실행 시 생성되어 내부 변경 사항을 담습니다.
- 읽기/쓰기 흐름
- 읽기: 가장 위 레이어부터 아래로 탐색
- 쓰기: CoW(Copy-on-Write) — 하위 레이어의 파일을 수정하면 그 파일 전체를 writable 레이어로 복사한 뒤 변경
- 삭제:
whiteout파일을 만들어 하위 레이어의 파일을 숨김 처리
⚠️ CoW 때문에 큰 파일을 자주 수정하면 복사 비용이 큽니다. 쓰기가 잦거나 영속이 필요한 데이터는 레이어가 아니라 볼륨(Volume) 에 두세요.
⚡ 이미지 최적화 실전 #
1) 불필요 파일 제거 — 한 레이어에서 정리 #
설치와 캐시 삭제를 같은 RUN 에서 처리해야 레이어에 캐시가 남지 않습니다.
1RUN apt-get update && apt-get install -y curl \
2 && rm -rf /var/lib/apt/lists/*2) 멀티스테이지 빌드(Multi-stage Build) #
빌드 도구는 빌드 단계에만 두고, 최종 이미지에는 산출물만 복사합니다.
1# Build stage
2FROM maven:3.9-jdk17 AS build
3WORKDIR /app
4COPY pom.xml .
5COPY src ./src
6RUN mvn clean package -DskipTests
7
8# Production stage
9FROM openjdk:17-jdk-slim
10WORKDIR /app
11COPY --from=build /app/target/myapp.jar .
12ENTRYPOINT ["java", "-jar", "myapp.jar"]✅ 결과: 최종 이미지에서 빌드 도구·캐시가 제거되어 크기가 크게 줄어듭니다.
3) 레이어 순서로 캐시 활용 #
Docker는 각 레이어를 SHA256 해시로 비교해 변하지 않은 레이어는 캐시를 재사용합니다. 자주 바뀌지 않는 것(의존성)을 위에, 자주 바뀌는 것(소스 코드)을 아래에 두세요. 순서가 잘못되면 코드 한 줄 바꿔도 의존성부터 다시 받습니다.
1# 의존성 먼저(잘 안 바뀜) → 캐시 적중
2COPY pom.xml .
3RUN mvn dependency:go-offline
4# 소스는 나중에(자주 바뀜)
5COPY src ./src4) .dockerignore
#
.git, node_modules, 로컬 빌드 산출물 등 빌드 컨텍스트에서 제외해 불필요한 레이어 증가를 막습니다.
🔍 이미지 크기·레이어 확인 #
1# 이미지 크기 확인
2docker images
3
4# 레이어별 크기·명령 확인
5docker history <image-name>💡 레이어별로 어디서 용량이 커지는지 시각적으로 분석하려면 오픈소스 도구
dive가 유용합니다. 레이어 선택 시 추가/변경된 파일 트리를 보여줍니다.
🤔 핵심 요약 #
- 이미지 크기 = OS + 라이브러리 + 애플리케이션 코드
- UFS(overlay2) = 읽기 전용 레이어 + 쓰기 레이어를 합쳐 제공, 쓰기는 CoW
- 최적화 3대 포인트: ① 불필요 파일 제거(한 레이어 정리) ② 멀티스테이지 빌드 ③ 레이어 순서로 캐시 활용 + 경량 베이스
❓ 자주 묻는 질문 #
Q. 레이어를 줄이려고 RUN을 하나로 합치면 무조건 좋나요?
캐시 측면에선 손해일 수 있습니다. 자주 바뀌는 단계와 안 바뀌는 단계는 분리해야 캐시가 살아납니다. “불필요 파일 정리"만 같은 RUN에서 처리하세요.
Q. 이미지를 가장 빠르게 줄이는 한 가지는?
멀티스테이지 빌드 + 경량 베이스(-slim/Distroless) 조합이 효과가 가장 큽니다.
Q. 쓰기가 많은 데이터는 어디에? 레이어(CoW)는 큰 파일 수정에 비싸므로 볼륨을 사용하세요.