infra
Platform

모듈 맵

[Docker] 레이어 캐싱 원리와 커스텀 이미지 레지스트리 태그 전략

0 / 27 완료

펼치기
0 / 27 완료0%

Docker · 06 / 27

[Docker] 레이어 캐싱 원리와 커스텀 이미지 레지스트리 태그 전략

레이어 아키텍처를 이해하고 Docker Hub에서 이미지를 pull/push합니다

🚨INCIDENT ALERT
HIGH

아침 배포 직후 운영 서버가 어제와 다른 nginx:latest 이미지를 내려받았습니다. 코드 변경은 없었는데 컨테이너 안의 패키지 버전이 달라져 장애 원인을 재현하기 어려웠습니다.

이미지는 레이어로 저장되고 태그는 언제든 다른 이미지를 가리킬 수 있습니다. Docker Hub에서 무엇을 pull했는지, 어떤 태그를 신뢰할지, 디스크가 찰 때 무엇을 지워도 되는지 모르면 배포와 정리가 모두 위험해집니다.

Docker 이미지 구조와 Registry

Docker의 강력함은 단순히 컨테이너를 실행하는 것에 그치지 않습니다. 이미지의 레이어 아키텍처를 이해하면 왜 docker pull이 빠른지, 왜 동일 베이스 이미지를 쓰는 컨테이너들이 디스크를 공유하는지 이해할 수 있습니다. 이 챕터에서는 이미지의 내부 구조부터 레지스트리 활용까지 살펴봅니다.


이번 챕터에서 배울 것

앞 모듈에서 pull/build/tag/push 워크플로우를 익혔다면, 이번엔 이미지가 내부적으로 어떻게 저장되는지, 레지스트리 생태계를 어떻게 활용하는지 깊이 들어갑니다.

  • 1Union Filesystem(OverlayFS)과 Copy-on-Write — 레이어가 디스크에 저장되는 방식
  • 2Docker Hub, GitHub Container Registry(GHCR), 사설 Registry 비교와 선택 기준
  • 3이미지 다이제스트(digest)와 immutable 태그 전략 — :latest를 쓰면 안 되는 이유
  • 4docker system df와 prune으로 디스크 공간 관리
실습 환경 준비

push 실습은 Docker Hub 계정이 필요합니다. 계정이 없으면 pull/inspect 실습만으로도 충분합니다.

Docker 실행 중인지 확인
docker info | grep 'Server Version'
Docker Hub 계정 생성 (무료)

https://hub.docker.com — push 실습 시 필요

Docker Hub 로그인
docker login
실습용 이미지 pull
docker pull ubuntu:22.04 && docker pull alpine:3.19
💡개념

레이어 아키텍처와 UnionFS — 이미지가 저장되는 방식

docker pull로 이미지를 받을 때 줄마다 'Pull complete', 'Already exists'가 번갈아 표시됩니다. 이게 레이어입니다. 이 구조를 이해하면 왜 같은 Alpine 이미지를 기반으로 한 이미지 10개가 디스크를 중복해서 차지하지 않는지, 컨테이너를 100개 실행해도 이미지가 100배로 늘어나지 않는지 설명됩니다. Dockerfile에서 명령어 순서가 빌드 속도에 영향을 주는 이유도 레이어 캐시 구조에서 나옵니다. 이 ConceptBlock에서는 레이어가 실제 디스크에 어떻게 저장되는지와 Copy-on-Write 전략을 다룹니다.

UnionFS 레이어 아키텍처 — 읽기 전용 레이어와 컨테이너 쓰기 레이어

레이어(Layer)란?

Docker 이미지는 여러 개의 읽기 전용 레이어가 쌓인 구조입니다. 각 레이어는 Dockerfile의 한 명령어(RUN, COPY, ADD 등)가 만들어내는 파일시스템 변경 사항의 스냅샷입니다.

┌────────────────────────────────────────────────────┐
│  컨테이너 레이어 (읽기/쓰기 가능)                     │  ← docker run 시 생성
├────────────────────────────────────────────────────┤
│  레이어 4: COPY . /app (앱 소스코드)                 │  ← 읽기 전용
├────────────────────────────────────────────────────┤
│  레이어 3: RUN pip install -r requirements.txt      │  ← 읽기 전용
├────────────────────────────────────────────────────┤
│  레이어 2: RUN apt-get install python3             │  ← 읽기 전용
├────────────────────────────────────────────────────┤
│  레이어 1: FROM ubuntu:22.04 (베이스 이미지)          │  ← 읽기 전용
└────────────────────────────────────────────────────┘

각 레이어는 SHA256 해시로 식별되며, Docker는 이 해시를 사용해 레이어를 캐시하고 재사용합니다.

UnionFS와 Copy-on-Write

Docker는 OverlayFS(또는 overlay2)라는 Union 파일시스템을 사용합니다. UnionFS는 여러 디렉토리(레이어)를 하나의 가상 파일시스템으로 병합하여 보여주는 기술입니다.

컨테이너에서 파일 읽기:
  1. 최상위 쓰기 레이어 확인
  2. 없으면 아래 레이어 순서대로 탐색
  3. 처음 발견된 파일 반환

컨테이너에서 파일 수정(Copy-on-Write):
  1. 해당 파일을 하위 읽기 전용 레이어에서 쓰기 레이어로 복사
  2. 쓰기 레이어에서 파일 수정
  3. 이후 읽기 시 쓰기 레이어의 파일이 우선

레이어 공유의 장점

동일한 베이스 이미지를 사용하는 여러 이미지는 하위 레이어를 공유합니다:

이미지 A (python-app):          이미지 B (node-app):
  레이어 4: COPY python-app     레이어 4: COPY node-app
  레이어 3: pip install         레이어 3: npm install
  레이어 2: python3 설치        레이어 2: nodejs 설치
  레이어 1: ubuntu:22.04 ────── ubuntu:22.04 (공유!)

ubuntu:22.04 레이어는 디스크에 한 번만 저장되며, 여러 이미지가 이를 공유합니다. 이는 디스크 절약과 pull 속도 향상으로 이어집니다.

OverlayFS 실제 구조 확인

Docker의 레이어는 호스트 파일시스템에서 직접 확인할 수 있습니다. overlay2 드라이버가 각 레이어를 별도 디렉토리로 관리하는 방식을 보면, 왜 동일 베이스 이미지를 쓰는 수십 개의 컨테이너가 디스크를 거의 추가로 차지하지 않는지 이해됩니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part2/exam_6 && cd /tmp/docker/part2/exam_6

# overlay2 드라이버 사용 확인
docker info | grep "Storage Driver"
# Storage Driver: overlay2

# 이미지 레이어 실제 저장 위치
sudo ls /var/lib/docker/overlay2/

# 특정 이미지의 레이어 ID 확인
docker image inspect nginx --format '{{json .RootFS.Layers}}'

💡개념

Docker Hub와 이미지 태그 관리

docker pull nginx를 실행하면 이미지가 내려옵니다. 그런데 이 nginx 이미지를 누가 관리하는지, 보안 패치는 제때 이루어지는지 확인하셨나요? Docker Hub에는 공식 이미지부터 누구나 올릴 수 있는 커뮤니티 이미지까지 수백만 개가 혼재합니다. 이름만 보고 신뢰하면 악의적으로 패키징된 이미지를 내려받을 수 있고, :latest 태그를 그대로 프로덕션에 사용하면 오늘과 내일의 배포 결과가 달라지는 상황을 만납니다. "어제까지 잘 됐는데 오늘 배포만 실패한다"는 문제의 절반은 태그 관리 부재에서 비롯됩니다. 이 ConceptBlock에서는 Docker Hub 이미지 신뢰 등급과 태그 전략을 다룹니다.

Docker Hub 이미지 등급 — 공식, 검증된 퍼블리셔, 커뮤니티

Docker Hub 이미지 종류

Docker Hub(hub.docker.com)는 세계 최대의 컨테이너 이미지 레지스트리입니다. 이미지는 신뢰 수준에 따라 구분됩니다.

공식 이미지 (Official Images)

Docker Inc.와 해당 소프트웨어 커뮤니티가 직접 관리합니다. 이미지 이름에 슬래시(/)가 없으며 Docker Hub에서 파란 "Official Image" 배지를 확인할 수 있습니다.

로컬 터미널
# 공식 이미지 예시 (사용자명 없음)
docker pull nginx          # Nginx 공식 이미지
docker pull postgres        # PostgreSQL 공식 이미지
docker pull python          # Python 공식 이미지
docker pull node            # Node.js 공식 이미지
docker pull ubuntu          # Ubuntu 공식 이미지
docker pull alpine          # Alpine Linux 공식 이미지

검증된 퍼블리셔 이미지 (Verified Publisher)

Docker가 신원을 확인한 기업/조직이 제공합니다.

로컬 터미널
# 검증된 퍼블리셔 이미지 (조직명/이미지명)
docker pull elastic/elasticsearch:8.11.0
docker pull bitnami/postgresql:15

커뮤니티 이미지

누구나 올릴 수 있는 이미지입니다. 사용 전 반드시 Dockerfile과 관리 상태를 검토해야 합니다.

로컬 터미널
# 커뮤니티 이미지 (사용자명/이미지명)
docker pull myusername/myapp:latest

태그(Tag) 관리의 중요성

태그는 이미지의 특정 버전을 식별하는 레이블입니다. 이미지 전체 참조 형식은 다음과 같습니다:

[레지스트리호스트:포트/][사용자명/]이미지명[:태그]

예시:
  nginx                              # Docker Hub 공식, latest 태그
  nginx:1.25.3                       # Docker Hub 공식, 특정 버전
  myuser/myapp:v2.1                  # Docker Hub 커뮤니티
  registry.example.com/myapp:1.0.0  # 프라이빗 레지스트리
  gcr.io/google-containers/pause:3.9 # Google Container Registry

:latest 태그의 위험성

:latest는 고정된 버전이 아닙니다. 누군가 새 이미지를 push할 때마다 가리키는 대상이 바뀝니다.

로컬 터미널
# 위험한 패턴 — 언제 이미지가 바뀔지 모름
FROM python:latest  # ❌ 프로덕션에서 절대 금지

# 안전한 패턴 — 정확한 버전 고정
FROM python:3.11.7-slim-bookworm  # ✅ 재현 가능한 빌드

:latest는 단순히 "가장 최근에 push된 이미지"를 가리키는 일반 태그입니다. 특별한 의미가 없으며, 이미지 관리자가 새 버전을 push할 때마다 변경될 수 있습니다.

태그 전략 예시

CI/CD 파이프라인에서 이미지를 빌드할 때는 버전 번호, Git SHA, 환경명을 조합해 태그를 붙입니다.

로컬 터미널
# 시맨틱 버저닝 기반 태그 전략
docker tag myapp:latest myapp:2.1.3      # 정확한 버전
docker tag myapp:latest myapp:2.1        # 마이너 버전
docker tag myapp:latest myapp:2          # 메이저 버전
docker tag myapp:latest myapp:stable     # 안정화 채널
docker tag myapp:latest myapp:latest     # 최신 (개발용)

# Git 커밋 해시 기반 태그 (CI/CD에서 많이 사용)
GIT_SHA=$(git rev-parse --short HEAD)
docker tag myapp:latest myapp:$GIT_SHA

Alpine vs slim vs 기본 이미지

공식 이미지는 보통 여러 변형(variant)을 제공합니다:

변형특징크기
python:3.11Debian 기반, 모든 패키지 포함~900MB
python:3.11-slimDebian 기반, 최소 패키지~125MB
python:3.11-alpineAlpine Linux, musl libc~50MB

Alpine은 크기가 작지만 musl libc 사용으로 일부 Python 패키지 빌드에 문제가 생길 수 있습니다. 일반적으로 slim 변형이 크기와 호환성의 균형이 좋습니다.


docker pull과 이미지 관리 기본 명령어

목표

Docker Hub에서 다양한 이미지를 pull하고, 로컬 이미지를 효율적으로 관리하는 방법을 익힙니다.

이 실습은 Docker Hub와 로컬 이미지를 다루는 순수 명령어 실습입니다. 별도 파일 생성 없이 바로 진행할 수 있습니다.

실습

1단계: 이미지 검색

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part1/exam_3 && cd /tmp/docker/part1/exam_3

# 이미지 태그 목록 확인용 스크립트 생성
cat > list-tags.sh << 'EOF'
#!/bin/bash
IMAGE=${1:-nginx}
echo "=== Docker Hub 이미지 태그 목록: $IMAGE ==="
curl -s "https://registry.hub.docker.com/v2/repositories/library/${IMAGE}/tags/?page_size=10" \
  | python3 -c "import sys,json; [print(t['name']) for t in json.load(sys.stdin)['results']]" 2>/dev/null \
  || echo "curl/python3 없는 환경에서는 docker search ${IMAGE} 사용"
EOF
chmod +x list-tags.sh

이제 실습을 진행합니다.

로컬 터미널
# Docker Hub에서 이미지 검색
docker search nginx

# NAME                              DESCRIPTION                      STARS   OFFICIAL
# nginx                             Official build of Nginx.          19000+  [OK]
# unit                              Official build of NGINX Unit.     108     [OK]
# ...

# 공식 이미지만 필터링
docker search --filter is-official=true nginx

# 별점 100개 이상 이미지 필터링
docker search --filter stars=100 python

2단계: 이미지 pull

특정 버전 태그나 SHA256 다이제스트로 pull하면 동일한 이미지를 재현할 수 있습니다.

로컬 터미널
# 기본 pull (latest 태그)
docker pull ubuntu

# 특정 버전 태그로 pull
docker pull ubuntu:20.04
docker pull ubuntu:22.04

# 특정 다이제스트(SHA256)로 pull (가장 안전한 방법)
docker pull ubuntu@sha256:b6b83d3c331794420340093eb706a6f152d9c1be1f6377a2e1f7b9b9a1d6e6c2

# 멀티플랫폼 이미지: 특정 아키텍처 명시
docker pull --platform linux/arm64 nginx:1.25

3단계: 로컬 이미지 목록 확인

pull한 이미지는 로컬에 캐시됩니다. 태그, ID, 크기를 확인하고 필요 없는 이미지는 정리합니다.

로컬 터미널
# 기본 목록
docker images
# REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
# ubuntu       22.04     c6b84b685f35   2 weeks ago    77.8MB
# ubuntu       20.04     1c5c8d0b973a   3 months ago   72.8MB
# nginx        latest    a6bd71f48f68   4 days ago     187MB

# 모든 이미지 (중간 레이어 포함)
docker images -a

# 특정 이미지만 필터링
docker images ubuntu

# 이미지 ID만 출력
docker images -q

# 포맷 지정 출력
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

4단계: 이미지 상세 정보 확인

로컬 터미널
# 전체 메타데이터 출력 (JSON)
docker image inspect ubuntu:22.04

# 레이어 구조만 확인
docker image inspect ubuntu:22.04 --format '{{json .RootFS.Layers}}' | python3 -m json.tool

# 환경 변수 확인
docker image inspect nginx --format '{{json .Config.Env}}'

# 노출 포트 확인
docker image inspect nginx --format '{{json .Config.ExposedPorts}}'

# 이미지 히스토리 (각 레이어 생성 명령어)
docker history nginx
# IMAGE          CREATED      CREATED BY                                      SIZE
# a6bd71f48f68   4 days ago   /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
# ...

5단계: 이미지 태그 추가

로컬 터미널
# 이미지에 새 태그 추가 (이미지 복사가 아닌 참조 추가)
docker tag ubuntu:22.04 my-ubuntu:latest
docker tag ubuntu:22.04 registry.mycompany.com/base/ubuntu:22.04

# 태그 확인
docker images ubuntu
docker images my-ubuntu
🔍실행 후 확인할 것
  • docker search nginx 출력에서 공식 이미지가 [OK]로 표시되는가?
  • docker pull ubuntu:22.04 후 docker images 목록에 해당 태그가 보이는가?
  • docker image inspect 결과에서 RootFS.Layers 배열을 확인했는가?
  • docker history nginx 출력으로 레이어별 생성 명령과 크기를 확인했는가?

Docker Hub 로그인과 이미지 push

목표

Docker Hub 계정에 로그인하고, 로컬에서 빌드한 이미지를 Docker Hub에 push합니다.

사전 조건

  • Docker Hub 계정 (hub.docker.com에서 무료 가입)
  • 로컬에 빌드된 이미지

실습

1단계: Docker Hub 로그인

로컬 터미널
# 대화형 로그인
docker login

# 사용자명과 패스워드 직접 지정 (CI/CD에서 사용)
docker login -u myusername -p mypassword

# Access Token 사용 (패스워드 대신 권장)
# Docker Hub → Account Settings → Security → New Access Token
docker login -u myusername --password-stdin << EOF
dckr_pat_xxxxxxxxxxxxxxxxxxxx
EOF

# 프라이빗 레지스트리 로그인
docker login registry.mycompany.com

2단계: 간단한 이미지 빌드

로컬 터미널
# 테스트용 Dockerfile 생성
mkdir -p /tmp/myapp && cat > /tmp/myapp/Dockerfile << 'EOF'
FROM alpine:3.18
RUN echo "Hello from my custom image" > /hello.txt
CMD ["cat", "/hello.txt"]
EOF

# 이미지 빌드 (myusername을 실제 Docker Hub 계정명으로 교체)
docker build -t myusername/hello-docker:1.0 /tmp/myapp

3단계: 이미지 push

로컬 터미널
# Docker Hub에 push
docker push myusername/hello-docker:1.0

# 여러 태그 동시 push
docker tag myusername/hello-docker:1.0 myusername/hello-docker:latest
docker push myusername/hello-docker:latest

# push 결과 확인 (Docker Hub 웹사이트에서도 확인 가능)
# https://hub.docker.com/r/myusername/hello-docker

4단계: push한 이미지 확인

로컬 터미널
# 로컬 이미지 삭제
docker rmi myusername/hello-docker:1.0
docker rmi myusername/hello-docker:latest

# Docker Hub에서 다시 pull
docker pull myusername/hello-docker:1.0

# 실행 확인
docker run --rm myusername/hello-docker:1.0
# Hello from my custom image

5단계: 로그아웃

로컬 터미널
# Docker Hub 로그아웃
docker logout

# 프라이빗 레지스트리 로그아웃
docker logout registry.mycompany.com

Dangling 이미지와 디스크 공간 관리

목표

이미지 빌드와 업데이트 과정에서 쌓이는 불필요한 이미지를 정리하여 디스크 공간을 확보합니다.

Dangling 이미지란?

docker build를 반복 실행하면 이전 빌드의 이미지가 태그 없이 남습니다. 이런 이미지를 Dangling 이미지라고 합니다.

Docker
# 첫 번째 빌드
docker build -t myapp:latest .
# → myapp:latest 생성 (이미지 ID: abc123)

# 코드 수정 후 두 번째 빌드
docker build -t myapp:latest .
# → myapp:latest는 새 이미지 (ID: def456)를 가리킴
# → abc123 이미지는 태그 없이 남음 (Dangling)

# Dangling 이미지 확인
docker images -f dangling=true
# REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
# <none>       <none>    abc123...      10 minutes ago  200MB

디스크 사용량 확인 및 정리

로컬 터미널
# Docker가 사용하는 디스크 공간 전체 확인
docker system df
# TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
# Images          10        3         2.5GB     1.8GB (72%)
# Containers      5         2         100MB     50MB (50%)
# Local Volumes   3         2         500MB     100MB (20%)
# Build Cache     30        0         800MB     800MB

# 상세 내용 확인
docker system df -v

# Dangling 이미지만 삭제
docker image prune

# 사용하지 않는 이미지 모두 삭제 (실행 중 컨테이너와 무관한 이미지)
docker image prune -a

# 개별 이미지 삭제
docker rmi nginx:1.24     # 특정 태그 삭제
docker rmi abc123def456   # 이미지 ID로 삭제

# 사용하지 않는 이미지 한 번에 삭제 (태그 없는 것만)
docker rmi $(docker images -f "dangling=true" -q)
위험 명령어이미지·컨테이너·네트워크와 함께 볼륨 데이터까지 삭제될 수 있습니다.

Docker 리소스와 볼륨 일괄 정리

안전한 실행 조건: 실습 환경이며 삭제 대상 볼륨에 데이터베이스나 업로드 파일이 없음을 확인한 경우에만 실행합니다.

실행 전 반드시 확인

  • docker system df -v로 삭제 범위를 확인했다
  • docker volume ls로 보존해야 할 볼륨이 없는지 확인했다
  • --volumes가 데이터 삭제를 포함한다는 점을 이해했다
docker system prune -a --volumes

위 항목을 모두 확인한 후 복사할 수 있습니다

위험 명령어컨테이너가 참조 중인 이미지까지 강제로 삭제하여 재시작·재현 흐름을 깨뜨릴 수 있습니다.

참조 중인 이미지 강제 삭제

안전한 실행 조건: 이미지 ID와 참조 컨테이너를 확인했고 다시 pull 또는 build 가능한 경우에만 실행합니다.

실행 전 반드시 확인

  • docker ps -a --filter ancestor=abc123def456로 참조 컨테이너를 확인했다
  • 삭제 대상 이미지가 운영 롤백에 필요한 태그가 아님을 확인했다
  • 이미지를 다시 받을 수 있는 레지스트리 또는 Dockerfile이 있다
docker rmi -f abc123def456

위 항목을 모두 확인한 후 복사할 수 있습니다

정기적인 정리 자동화

로컬 터미널
# cron으로 매일 새벽 3시에 정리 실행 (dangling 이미지만)
# crontab -e 에 추가:
0 3 * * * docker image prune -f >> /var/log/docker-prune.log 2>&1

# 또는 사용량 임계값 기반 정리 스크립트
#!/bin/bash
THRESHOLD=80  # 디스크 사용률 80% 초과 시 정리
USAGE=$(df /var/lib/docker | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
  echo "디스크 사용률 ${USAGE}% — Docker 이미지 정리 시작"
  docker image prune -af
fi

문제 상황

로컬 터미널
$ docker pull python:3.11
Error response from daemon: failed to register layer: Error processing tar file
(exit status 1): write /usr/lib/...: no space left on device

또는

로컬 터미널
$ docker build -t myapp .
error: failed to solve: failed to create symlink ...:
no space left on device

원인 분석

Docker 이미지, 컨테이너, 볼륨, 빌드 캐시는 기본적으로 /var/lib/docker에 저장됩니다. 이 경로가 속한 파티션의 공간이 부족하면 위 에러가 발생합니다.

진단

로컬 터미널
# 전체 디스크 사용량 확인
df -h /var/lib/docker
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sda1        50G   48G  1.2G  98% /

# Docker별 사용량 확인
docker system df

# 가장 큰 이미지 확인
docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -rh | head -10

해결 방법 (긴급 순서대로)

1단계: 즉시 공간 확보

로컬 터미널
# 중지된 컨테이너 모두 삭제
docker container prune -f

# Dangling 이미지 삭제
docker image prune -f

# 전체 미사용 리소스 삭제
위험 명령어BuildKit/빌더 캐시가 삭제되어 이후 이미지 빌드 시간이 크게 늘어날 수 있습니다.

빌드 캐시 강제 삭제

안전한 실행 조건: 디스크 부족 대응 중이고 캐시를 재생성해도 되는 경우에만 실행합니다.

실행 전 반드시 확인

  • docker system df로 Build Cache 용량을 확인했다
  • 현재 진행 중인 빌드가 없음을 확인했다
  • 다음 빌드에서 캐시 미스로 시간이 늘어날 수 있음을 이해했다
docker builder prune -af

위 항목을 모두 확인한 후 복사할 수 있습니다

위험 명령어확인 프롬프트 없이 미사용 이미지·컨테이너·네트워크·빌드 캐시를 삭제합니다.

전체 미사용 Docker 리소스 강제 정리

안전한 실행 조건: 디스크 부족 대응 중이며 삭제 범위를 먼저 확인한 경우에만 실행합니다.

실행 전 반드시 확인

  • docker system df로 Docker 디스크 사용량을 확인했다
  • 실행 중인 컨테이너에서 사용하는 이미지는 삭제되지 않는다는 범위를 이해했다
  • 필요한 로컬 빌드 이미지가 다시 빌드 또는 pull 가능하다
docker system prune -af

위 항목을 모두 확인한 후 복사할 수 있습니다

2단계: Docker 데이터 경로 변경 (근본 해결)

파티션 자체가 작다면 더 큰 파티션으로 Docker 데이터를 이동합니다.

로컬 터미널
# Docker 데몬 설정 파일 편집
sudo nano /etc/docker/daemon.json

# 다음 내용 추가 (새 데이터 경로)
{
  "data-root": "/data/docker"
}

# 새 디렉토리에 기존 데이터 복사
sudo systemctl stop docker
sudo mkdir -p /data/docker
sudo rsync -aP /var/lib/docker/ /data/docker/

# Docker 재시작
sudo systemctl start docker

# 확인
docker info | grep "Docker Root Dir"

3단계: .dockerignore로 빌드 컨텍스트 크기 감소

빌드 컨텍스트가 크면 Docker 데몬에 불필요한 파일까지 전송되어 시간과 공간을 낭비합니다. .dockerignore로 제외할 파일을 명시합니다.

로컬 터미널
# 빌드 컨텍스트 크기 확인 (build 시 "Sending build context" 메시지)
# Sending build context to Docker daemon  500MB   ← 너무 크면 문제

# .dockerignore 파일 생성
cat > .dockerignore << 'EOF'
node_modules
.git
*.log
dist
coverage
.env
*.md
.DS_Store
EOF

문제 상황

로컬 터미널
$ docker pull myapp:production
Error response from daemon: manifest for myapp:production not found:
manifest unknown: manifest unknown

또는

로컬 터미널
$ docker pull mycompany/myapp:v2.5
Error response from daemon: pull access denied for mycompany/myapp,
repository does not exist or may require 'docker login'

원인 분석

에러 메시지원인
manifest unknown지정한 태그가 레지스트리에 없음
pull access denied인증 필요(private 이미지) 또는 이미지 자체가 없음
repository does not exist이미지 이름 오타

해결 방법

태그 오타 확인

로컬 또는 서버
# Docker Hub에서 사용 가능한 태그 목록 확인 (API 사용)
curl -s "https://hub.docker.com/v2/repositories/library/python/tags/?page_size=20" \
  | python3 -m json.tool | grep '"name"'

# 또는 웹 브라우저에서 확인
# https://hub.docker.com/_/python/tags

Private 레지스트리 인증

로컬 터미널
# 로그인 후 재시도
docker login registry.mycompany.com
docker pull registry.mycompany.com/myapp:v2.5

아키텍처 불일치 확인

로컬 터미널
# ARM64 호스트에서 AMD64 전용 이미지 pull 시도 시 에러
# --platform으로 명시적 지정
docker pull --platform linux/amd64 myapp:v2.5

로컬에서 이미지 존재 여부 확인

로컬 터미널
# 로컬 이미지 중 특정 이름 검색
docker images | grep myapp

# 이미지가 로컬에 있는지 확인 후 pull
docker image inspect myapp:v2.5 2>/dev/null && echo "로컬에 존재" || echo "pull 필요"

💼
실무 맥락
현업 패턴

엔터프라이즈 이미지 관리 체계

현업에서는 Docker Hub 공개 이미지를 그대로 프로덕션에서 사용하지 않고, 조직 내부 레지스트리를 통해 관리합니다.

프라이빗 레지스트리 구성

많은 기업들이 다음 중 하나를 사용합니다:

레지스트리특징적합 환경
Docker Hub (private)가장 간단소규모 팀
AWS ECRAWS 통합AWS 인프라
Google Artifact RegistryGCP 통합GCP 인프라
Harbor오픈소스 자체 호스팅온프레미스
GitLab Container RegistryGitLab 통합GitLab 사용 조직

이미지 보안 스캐닝

외부 이미지를 프로덕션에 그대로 올리면 알려진 CVE를 함께 배포하는 셈입니다. 빌드 단계에서 취약점을 자동으로 검사합니다.

로컬 터미널
# Docker Scout (Docker 공식 보안 스캐닝)
docker scout cves nginx:1.25.3

# Trivy (오픈소스 취약점 스캐너)
trivy image nginx:1.25.3

# 취약점 발견 시 자동으로 빌드 실패시키는 CI/CD 설정 예시
# .github/workflows/security.yml

이미지 라이프사이클 정책

실제 팀에서는 이미지 버전을 언제 삭제할지 정책을 수립합니다:

CI/CD에서의 이미지 빌드와 push

코드가 main 브랜치에 머지될 때마다 이미지를 자동으로 빌드하고 레지스트리에 올리는 것이 표준 패턴입니다. 커밋 SHA를 태그로 쓰면 배포 이력을 그대로 추적할 수 있습니다.

YAML
# GitHub Actions 예시: 코드 push 시 이미지 빌드 및 push
name: Build and Push
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Login to ECR
        uses: aws-actions/amazon-ecr-login@v2
      - name: Build and push
        run: |
          IMAGE_TAG=${{ github.sha }}
          docker build -t $ECR_REGISTRY/myapp:$IMAGE_TAG .
          docker push $ECR_REGISTRY/myapp:$IMAGE_TAG
          # main 브랜치는 latest도 함께 태그
          docker tag $ECR_REGISTRY/myapp:$IMAGE_TAG $ECR_REGISTRY/myapp:latest
          docker push $ECR_REGISTRY/myapp:latest

이러한 전략들은 애플리케이션 배포의 안정성과 재현성을 보장하며, 보안 취약점을 조기에 발견할 수 있게 합니다.


💡개념

이미지 이름의 완전한 구조 — registry/namespace/repo:tag@digest

로컬에서 docker build -t myapp:1.0 .으로 이미지를 만든 뒤 docker push myapp:1.0을 실행하면 "denied: access forbidden" 에러가 납니다. 회사 ECR이나 사설 레지스트리에 올리려면 이미지 이름 앞에 레지스트리 주소가 포함되어야 하는데, myapp:1.0은 Docker Hub의 개인 계정을 가정하기 때문입니다. 또 nginx라는 이름은 실제로는 docker.io/library/nginx:latest가 생략된 형태이고, AWS ECR 이미지는 123456.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:latest처럼 보입니다. 이 규칙을 모르면 push 실패, pull 실패, 이미지 이름 혼동이 반복됩니다. 이 ConceptBlock에서는 이미지 이름의 전체 구성과 다이제스트로 불변성을 보장하는 방법을 다룹니다.

이미지 이름 전체 구조 — registry/namespace/repo:tag@digest

이미지 이름의 전체 형식

Docker 이미지 이름은 여러 부분으로 구성되며, 생략 시 기본값이 채워집니다.

전체 형식:
[레지스트리호스트[:포트]/][네임스페이스/]저장소[:태그][@다이제스트]

예시:
docker.io/library/nginx:1.25@sha256:abc123...def456
    ↑          ↑       ↑      ↑          ↑
 레지스트리  네임스페이스 저장소  태그     다이제스트
로컬 터미널
# 생략 시 기본값 채우기
nginx                = docker.io/library/nginx:latest
nginx:alpine         = docker.io/library/nginx:alpine
myuser/myapp         = docker.io/myuser/myapp:latest
myuser/myapp:v1.0    = docker.io/myuser/myapp:v1.0

# 다른 레지스트리 사용
ghcr.io/myorg/myapp:latest          # GitHub Container Registry
123456.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:latest  # AWS ECR
asia-northeast1-docker.pkg.dev/project/repo/myapp:latest  # GCP Artifact Registry
myregistry.internal:5000/myapp:v2   # 사설 레지스트리

태그 vs 다이제스트 — 가변 vs 불변

태그는 편하지만 언제든 덮어쓸 수 있어 재현성이 깨질 수 있습니다. 다이제스트는 SHA256 해시이므로 항상 동일한 이미지를 가리킵니다.

로컬 터미널
# 태그: 가변 (같은 태그가 언제든 다른 이미지를 가리킬 수 있음)
docker pull nginx:latest
# 오늘의 latest와 내일의 latest가 다를 수 있음!

# 다이제스트: 불변 (SHA256 해시, 항상 동일한 이미지 보장)
docker pull nginx@sha256:a0bcfe...8d12c
# 언제 어디서 pull해도 완전히 동일한 이미지

# 현재 이미지의 다이제스트 확인
docker image inspect nginx:latest --format '{{.RepoDigests}}'
# [nginx@sha256:a0bcfe...8d12c]

# 다이제스트로 pull (프로덕션 환경 권장)
docker pull nginx@sha256:a0bcfe...8d12c

사설 레지스트리 이미지 관리

로컬에서 빌드한 이미지를 사설 레지스트리에 push할 때는 이미지 이름에 레지스트리 주소를 포함한 태그를 먼저 추가합니다.

로컬 터미널
# 로컬에서 빌드한 이미지를 사설 레지스트리에 push
# 1. 이미지에 레지스트리 주소 포함한 태그 추가
docker tag myapp:latest myregistry.example.com:5000/myapp:latest
docker tag myapp:latest myregistry.example.com:5000/myapp:v1.2.3

# 2. 레지스트리에 push
docker push myregistry.example.com:5000/myapp:latest
docker push myregistry.example.com:5000/myapp:v1.2.3

# 3. 다른 서버에서 pull
docker pull myregistry.example.com:5000/myapp:v1.2.3

# 4. 이미지 목록 API 확인
curl http://myregistry.example.com:5000/v2/_catalog
# {"repositories":["myapp","nginx","redis"]}
curl http://myregistry.example.com:5000/v2/myapp/tags/list
# {"name":"myapp","tags":["latest","v1.2.3","v1.2.2"]}

💡개념

이미지 정리와 디스크 관리 — docker system df와 prune

CI 빌드 서버를 3개월째 운영하다 보면 어느 날 아침 파이프라인 전체가 멈춥니다. docker build 도중 "no space left on device" 에러가 납니다. 원인은 매번 빌드할 때마다 생성되는 dangling 이미지가 아무도 정리하지 않은 채 쌓인 것입니다. 개발 노트북에서도 Docker를 몇 달 쓰면 디스크 수 GB가 Docker 이미지와 빌드 캐시로 채워집니다. docker system df를 처음 실행했을 때 "회수 가능 공간 3.1GB"가 표시되면 당황하기 마련입니다. 정리 명령어는 여러 종류가 있고 영향 범위가 다르므로, 순서와 범위를 알고 써야 실수로 운영 데이터를 지우는 사고를 막을 수 있습니다. 이 ConceptBlock에서는 Docker 디스크 사용량을 파악하고 안전하게 정리하는 방법을 다룹니다.

이미지 정리 순서 — dangling, 미사용, 전체 prune 범위

디스크 사용량 파악

Docker를 오래 사용하면 이미지, 컨테이너, 볼륨, 빌드 캐시가 쌓여 디스크를 차지합니다.

로컬 터미널
# Docker 전체 디스크 사용량 확인
docker system df
# TYPE            TOTAL   ACTIVE  SIZE      RECLAIMABLE
# Images          12      3       4.2GB     3.1GB (73%)  ← 회수 가능
# Containers      5       2       12MB      1kB (0%)
# Local Volumes   8       3       2.1GB     800MB (38%)
# Build Cache     23      0       1.5GB     1.5GB        ← 전부 회수 가능

# 상세 목록
docker system df -v

dangling 이미지가 생기는 이유

Docker
# 같은 태그로 재빌드하면 이전 이미지가 <none>:<none> 으로 남음
docker build -t myapp:latest .   # 첫 번째 빌드 → abc123
docker build -t myapp:latest .   # 재빌드 → def456 (새 이미지)
                                  # abc123은 태그를 잃고 <none>:<none>으로 전락

docker images
# REPOSITORY   TAG       IMAGE ID       SIZE
# myapp        latest    def456         200MB  ← 현재 이미지
# <none>       <none>    abc123         200MB  ← dangling 이미지!

단계별 정리 방법

영향 범위가 작은 것부터 순서대로 실행합니다. 운영 환경에서는 prune -a--volumes 사용 전에 반드시 어떤 데이터가 삭제되는지 확인해야 합니다.

로컬 터미널
# 1. dangling 이미지만 삭제 (가장 안전)
docker image prune
# WARNING! This will remove all dangling images.
# 삭제할 공간: 1.2GB

# 2. 사용 중이지 않은 이미지 전체 삭제
docker image prune -a
# 실행 중인 컨테이너에서 사용 중인 이미지는 삭제 안 됨

# 3. 미사용 컨테이너 삭제
docker container prune

# 4. 미사용 볼륨 삭제 (주의: 데이터 영구 삭제!)

# 5. 한 번에 전체 정리
위험 명령어미사용 볼륨 데이터가 영구 삭제되어 데이터베이스나 업로드 파일을 잃을 수 있습니다.

볼륨 포함 Docker 전체 정리

안전한 실행 조건: 삭제 대상이 실습/임시 리소스뿐이고 볼륨 백업이 끝난 경우에만 실행합니다.

실행 전 반드시 확인

  • docker volume ls -f dangling=true로 삭제 후보 볼륨을 확인했다
  • 볼륨 이름과 용도를 확인했다
  • 보존할 데이터의 백업 또는 재생성 방법이 있다
docker system prune -a --volumes

위 항목을 모두 확인한 후 복사할 수 있습니다

자동 정리 설정

--filter 옵션으로 기간이나 라벨을 지정하면 원하는 범위만 정리할 수 있습니다. CI 빌드 서버에는 cron으로 등록해 두면 디스크가 차는 것을 예방할 수 있습니다.

로컬 터미널
# 특정 날짜 이전 이미지만 삭제
docker image prune -a --filter "until=72h"  # 72시간 이상 미사용

# 특정 라벨이 있는 이미지만 삭제
docker image prune --filter "label=stage=dev"

# cron으로 주기적 자동 정리 (매주 일요일 새벽 3시)

정리

핵심 명령어 요약

이 모듈에서 다룬 이미지 관련 명령어를 한 곳에 정리합니다.

로컬 터미널
# 이미지 pull
docker pull ubuntu:22.04

# 로컬 이미지 목록
docker images
docker images -a            # 중간 레이어 포함

# 이미지 상세 확인
docker image inspect ubuntu:22.04
docker history nginx

# 태그 추가
docker tag myapp:1.0 registry.example.com/myapp:1.0

# Docker Hub 로그인/push
docker login
docker push myuser/myapp:1.0

# 이미지 삭제 및 정리
docker rmi nginx:old
docker image prune           # dangling만
docker image prune -a        # 미사용 전체
docker system df             # 디스크 사용량 확인

다음 챕터 예고

다음 모듈에서는 직접 Dockerfile을 작성하여 애플리케이션 이미지를 빌드하는 방법을 학습합니다. FROM, RUN, COPY, CMD 등 핵심 명령어와 레이어 캐시 최적화를 통해 빠르고 작은 이미지를 만드는 기술을 다룹니다.

지식 확인

퀴즈 — 5문제

Q1

Docker 이미지의 레이어(Layer) 아키텍처에서 Copy-on-Write(CoW) 전략이 의미하는 것은?

Q2

Docker Hub에서 이미지를 사용할 때 `:latest` 태그를 프로덕션에서 사용하면 안 되는 이유는?

Q3

`docker image prune -a` 명령어는 어떤 이미지를 삭제하나요?

Q4

동일한 이미지 내용이지만 여러 레지스트리에 push하기 위해 이미지에 새 태그를 붙이는 올바른 명령어는?

Q5

Docker Hub의 공식(Official) 이미지와 검증된(Verified Publisher) 이미지의 차이점은?

0 / 5 답변

🧪 실습으로 확인하기

Docker Compose 멀티 서비스 구성

초급

docker-compose.yml로 nginx + 앱 컨테이너를 함께 정의하고, 서비스 간 통신과 볼륨 마운트를 구성한다.

35📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

docker중급 · 45
[Docker] 사용하지 않는 이미지 정리와 최적의 태그 아카이빙 전략
Docker 트랙 계속
networking입문 · 45
[Network] OSI 7계층과 TCP/IP 4계층 모델 실무적 관점 분석
Networking 트랙 시작점