카테고리 없음

Docker 기본 - 용량 완전 정복

cell-devlog 2026. 5. 21. 10:28
반응형

컨테이너·이미지·볼륨·빌드캐시가 디스크를 어떻게 차지하는지, 어떻게 확인하고 줄이는지 전부 정리


1. 도커 전체 디스크 사용량 한눈에 보기

# 요약 보기
docker system df

# 상세 보기 (항목별 전부)
docker system df -v

출력 예시

TYPE            TOTAL   ACTIVE  SIZE      RECLAIMABLE
Images          12      3       18.5GB    14.2GB (76%)
Containers      5       2       1.2GB     800MB (66%)
Local Volumes   8       3       45GB      30GB (66%)
Build Cache     -       -       3.1GB     3.1GB
항목 설명
Images 로컬에 있는 이미지 총 용량
Containers 실행/중지된 컨테이너의 쓰기 레이어 용량
Local Volumes 도커 볼륨 총 용량
Build Cache docker build 빌드 캐시
RECLAIMABLE prune으로 회수 가능한 용량

2. 이미지 용량

이미지 목록 + 용량

# 이미지 목록 (SIZE 컬럼 포함)
docker images

# 용량 기준 정렬 (큰 것부터)
docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -rh

# 특정 이미지 상세 용량 정보
docker inspect <이미지> --format='{{.Size}}'

이미지 레이어 구조 확인

# 레이어별 용량 히스토리
docker history <이미지>

# 더 상세하게 (잘리지 않게)
docker history --no-trunc <이미지>

# 용량만 보기
docker history --format "{{.Size}}\t{{.CreatedBy}}" <이미지>

출력 예시

IMAGE          CREATED BY                                      SIZE
a1b2c3d4e5f6   CMD ["python", "app.py"]                        0B
<missing>      COPY . /app                                     512MB
<missing>      RUN pip install -r requirements.txt             1.2GB
<missing>      FROM python:3.11                                1.0GB

이미지 용량이 커지는 이유

# ❌ 레이어가 쌓여서 용량 낭비
RUN apt-get update
RUN apt-get install -y curl wget
RUN rm -rf /var/lib/apt/lists/*    # 이미 별도 레이어에 파일이 올라가서 의미 없음

# ✅ 한 레이어에 묶어야 효과 있음
RUN apt-get update && \
    apt-get install -y curl wget && \
    rm -rf /var/lib/apt/lists/*

3. 컨테이너 용량

컨테이너가 차지하는 용량 확인

# SIZE 컬럼으로 확인 (쓰기 레이어 / 가상 전체)
docker ps -s

# 특정 컨테이너
docker ps -s -f name=<컨테이너명>

docker ps -s SIZE 컬럼 해석

NAMES          SIZE
my_container   150MB (virtual 2.3GB)
항목 의미
150MB 컨테이너가 실제로 추가로 쓴 쓰기 레이어 크기
virtual 2.3GB 이미지 레이어 + 쓰기 레이어 합산 (실제 디스크 점유와 다름)

핵심: 이미지 레이어는 여러 컨테이너가 공유하므로 virtual 용량을 단순 합산하면 실제보다 훨씬 크게 보임.

컨테이너 상세 용량 확인

# 컨테이너 전체 파일시스템 크기
docker inspect <컨테이너명> --format='{{.SizeRootFs}}'

# 쓰기 레이어 크기만
docker inspect <컨테이너명> --format='.SizeRw}}'

# 모든 컨테이너 쓰기 레이어 용량 정렬
docker ps -s --format "{{.Size}}\t{{.Names}}" | sort -rh

컨테이너 내부 용량 확인

# 컨테이너 내부에서 df 실행
docker exec <컨테이너명> df -h

# 컨테이너 내부 특정 디렉토리 용량
docker exec <컨테이너명> du -sh /app

# 큰 파일 찾기
docker exec <컨테이너명> find / -size +100M -type f 2>/dev/null

컨테이너 로그 파일 용량

# 로그 파일 위치 확인
docker inspect --format='{{.LogPath}}' <컨테이너명>

# 로그 파일 크기 확인
ls -lh $(docker inspect --format='{{.LogPath}}' <컨테이너명>)

# 전체 컨테이너 로그 용량 합산
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null

4. 볼륨 용량

# 볼륨 목록
docker volume ls

# 특정 볼륨 상세 (마운트 경로 확인)
docker volume inspect <볼륨명>

# 볼륨 실제 경로 용량 확인
du -sh $(docker volume inspect --format='{{.Mountpoint}}' <볼륨명>)

# 전체 볼륨 용량 한 번에
docker volume ls -q | xargs -I{} sh -c \
  'echo -n "{}: "; du -sh $(docker volume inspect --format="{{.Mountpoint}}" {}) 2>/dev/null | cut -f1'

5. 빌드 캐시 용량

# 빌드 캐시 확인
docker system df

# 빌드 캐시만 상세히
docker builder du

# 빌드 캐시 삭제
docker builder prune -f

# 특정 기간 이전 캐시만 삭제
docker builder prune --filter until=24h -f

6. 도커 데이터 저장 위치 (호스트 기준)

# 기본 경로 (Linux)
/var/lib/docker/

# 세부 구조
/var/lib/docker/
├── containers/     # 컨테이너 메타데이터 + 로그
│   └── <ID>/
│       └── <ID>-json.log   # 로그 파일
├── image/          # 이미지 메타데이터
├── overlay2/       # 이미지·컨테이너 레이어 실제 데이터 (핵심!)
├── volumes/        # 도커 볼륨 데이터
└── network/        # 네트워크 설정

# 전체 도커 데이터 디렉토리 용량
du -sh /var/lib/docker/

# 레이어별 용량 (overlay2가 대부분을 차지함)
du -sh /var/lib/docker/overlay2/

7. 용량 아끼는 실전 전략

로그 용량 제한 (컨테이너 실행 시)

# 로그 드라이버 json-file, 최대 100MB, 3개 파일 순환
docker run \
  --log-driver json-file \
  --log-opt max-size=100m \
  --log-opt max-file=3 \
  <이미지>

로그 용량 제한 (daemon.json 전체 적용)

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
# 적용
sudo systemctl restart docker

도커 데이터 루트 경로 변경

// /etc/docker/daemon.json
{
  "data-root": "/mnt/data/docker"
}

기본 경로(/var/lib/docker)가 있는 파티션이 꽉 찰 때 용량 큰 디스크로 옮기는 방법.

Dockerfile 용량 최적화

# ✅ 멀티스테이지 빌드로 최종 이미지 용량 최소화
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt

FROM python:3.11-slim        # 슬림 이미지 사용
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
CMD ["python", "app.py"]

# ✅ .dockerignore 활용
# .dockerignore 파일에 아래 추가
node_modules/
__pycache__/
*.log
.git/
.env

8. 이미지 빌드 시 용량 줄이는 방법

8-1. 베이스 이미지 선택

# ❌ 풀 이미지 — 불필요한 패키지 전부 포함
FROM python:3.11          # ~1.0GB
FROM node:20              # ~1.1GB
FROM ubuntu:22.04         # ~77MB (OS만 있어도 패키지 깔면 금방 불어남)

# ✅ slim — 최소 패키지만 포함
FROM python:3.11-slim     # ~130MB
FROM node:20-slim         # ~240MB

# ✅ alpine — 가장 작음 (musl libc 기반, 일부 패키지 호환 주의)
FROM python:3.11-alpine   # ~50MB
FROM node:20-alpine       # ~130MB

# ✅ distroless — OS 셸도 없음, 보안 최강 (Google 제공)
FROM gcr.io/distroless/python3
FROM gcr.io/distroless/nodejs20

alpine은 musl libc를 써서 일부 C 확장 패키지(numpy, pandas 등)가 빌드 오래 걸리거나 깨질 수 있음. ML 환경엔 slim 권장.


8-2. 멀티스테이지 빌드 (가장 효과 큼)

빌드 도구·컴파일러·캐시를 최종 이미지에서 제거하는 핵심 기법.

# Python 예시
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.11-slim AS runtime
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
# 결과: 1.2GB → 280MB
# Node.js 예시
FROM node:20 AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
# 결과: 1.1GB → 300MB
# Go 예시 (바이너리 하나만 복사 — 극단적으로 작음)
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

FROM scratch                  # 완전 빈 이미지
COPY --from=builder /app/server /server
CMD ["/server"]
# 결과: 1.2GB → 10~20MB

8-3. RUN 레이어 최소화

도커는 RUN 명령어 하나당 레이어 하나를 생성함. 레이어가 많을수록 용량 낭비.

# ❌ 레이어 3개 생성 — 중간 삭제가 의미 없음
RUN apt-get update
RUN apt-get install -y curl wget git
RUN rm -rf /var/lib/apt/lists/*

# ✅ 레이어 1개 — 삭제가 같은 레이어에서 이루어짐
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl wget git && \
    rm -rf /var/lib/apt/lists/*
# ❌ pip 캐시가 레이어에 남음
RUN pip install -r requirements.txt

# ✅ 캐시 없이 설치
RUN pip install --no-cache-dir -r requirements.txt
# ❌ npm 캐시 잔존
RUN npm install

# ✅ 프로덕션 의존성만 + 캐시 정리
RUN npm ci --only=production && npm cache clean --force

8-4. .dockerignore 철저히 활용

COPY . . 할 때 불필요한 파일이 이미지에 들어가는 것을 막음.

# .dockerignore

# 버전 관리
.git/
.gitignore

# 의존성 (컨테이너 안에서 다시 설치)
node_modules/
vendor/

# 파이썬 캐시
__pycache__/
*.pyc
*.pyo
.pytest_cache/
*.egg-info/

# 환경 설정 (보안!)
.env
.env.*
*.key
*.pem
secrets/

# 로그
*.log
logs/

# 빌드 결과물
dist/
build/
*.o
*.a

# 개발 도구
.vscode/
.idea/
*.swp

# 테스트
tests/
test/
coverage/

# 도커 관련 (불필요)
Dockerfile*
docker-compose*.yml

8-5. COPY는 필요한 것만, 순서도 중요

# ❌ 전체 복사 후 설치 — 코드 변경마다 캐시 무효화
COPY . .
RUN pip install -r requirements.txt

# ✅ 의존성 파일 먼저 복사 → 설치 → 코드 복사
# requirements.txt 안 바뀌면 pip install 레이어 캐시 재사용
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

레이어 캐시 원칙: 자주 바뀌는 것(소스코드)은 아래에, 거의 안 바뀌는 것(의존성)은 위에.


8-6. 불필요한 패키지 설치 안 하기

# apt — 추천 패키지 제외
RUN apt-get install -y --no-install-recommends <패키지>

# apt — 문서·맨페이지 제외 (dpkg 설정)
RUN echo 'path-exclude=/usr/share/doc/*' > /etc/dpkg/dpkg.cfg.d/nodoc && \
    echo 'path-exclude=/usr/share/man/*' >> /etc/dpkg/dpkg.cfg.d/nodoc && \
    apt-get install -y --no-install-recommends <패키지>

# pip — 불필요한 extras 제외
RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cu118

# conda — 캐시 정리
RUN conda install <패키지> && conda clean -afy

8-7. 빌드 후 이미지 용량 분석 도구

# dive — 레이어별 파일 변경 내역 시각적으로 분석
# https://github.com/wagoodman/dive
docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive:latest <이미지:태그>

# docker history로 빠르게 확인
docker history --format "{{.Size}}\t{{.CreatedBy}}" <이미지> | sort -rh

# 이미지 전체 크기
docker inspect <이미지> --format='{{.Size}}' | \
  awk '{printf "%.1f MB\n", $1/1024/1024}'

8-8. 용량 감소 효과 비교표

기법 감소 효과 난이도
slim/alpine 베이스 이미지 사용 ★★★★★ 낮음
멀티스테이지 빌드 ★★★★★ 중간
.dockerignore 작성 ★★★☆☆ 낮음
RUN 레이어 합치기 ★★★☆☆ 낮음
--no-cache-dir / --no-install-recommends ★★☆☆☆ 낮음
COPY 순서 최적화 (빌드 속도 개선) ★☆☆☆☆ 낮음
distroless / scratch 이미지 ★★★★★ 높음

9. 용량 정리 명령어 총정리

# exited 컨테이너 삭제
docker container prune -f

# dangling 이미지(태그 없는) 삭제
docker image prune -f

# 미사용 이미지 전부 삭제
docker image prune -af

# 미사용 볼륨 삭제
docker volume prune -f

# 미사용 네트워크 삭제
docker network prune -f

# 빌드 캐시 삭제
docker builder prune -f

# 위 전부 한 번에 (컨테이너+이미지+네트워크+캐시)
docker system prune -f

# 볼륨까지 포함 (주의! 데이터 날아감)
docker system prune -af --volumes

10. 용량 모니터링 스크립트

#!/bin/bash
# docker_disk_report.sh

echo "===== Docker 디스크 사용량 리포트 ====="
echo ""

echo "[전체 요약]"
docker system df
echo ""

echo "[컨테이너별 쓰기 레이어 용량 TOP 10]"
docker ps -s --format "{{.Size}}\t{{.Names}}" | sort -rh | head -10
echo ""

echo "[이미지 용량 TOP 10]"
docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -rh | head -10
echo ""

echo "[로그 파일 총 용량]"
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | \
  awk '{sum+=$1} END {print sum "MB total"}'
echo ""

echo "[회수 가능한 용량]"
docker system df | awk 'NR>1 {print $1": "$NF}'

11. 자주 묻는 것들

Q. 이미지 삭제했는데 용량이 안 줄어요

# dangling 레이어가 남아있을 수 있음
docker image prune -f

# 그래도 안 줄면 실행 중 컨테이너가 이미지 참조 중
docker ps -a  # 컨테이너 확인 후 삭제

Q. overlay2 디렉토리가 너무 큰데 어떻게 해요

# overlay2는 이미지+컨테이너 레이어 저장소
# 안전하게 줄이는 방법
docker system prune -af    # 미사용 전부 정리
docker builder prune -f    # 빌드 캐시 정리

Q. 로그가 디스크를 다 먹어요

# 즉시 로그 비우기 (컨테이너 재시작 없이)
truncate -s 0 $(docker inspect --format='{{.LogPath}}' <컨테이너명>)

Q. 볼륨이 어느 컨테이너가 쓰는지 모르겠어요

# 볼륨 사용 중인 컨테이너 확인
docker ps -a --filter volume=<볼륨명>

# 또는 inspect로
docker inspect <볼륨명>

반응형