아침 배포 직후 운영 서버가 어제와 다른 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 info | grep 'Server Version'https://hub.docker.com — push 실습 시 필요
docker logindocker 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 전략을 다룹니다.

레이어(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(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.11 | Debian 기반, 모든 패키지 포함 | ~900MB |
python:3.11-slim | Debian 기반, 최소 패키지 | ~125MB |
python:3.11-alpine | Alpine Linux, musl libc | ~50MB |
Alpine은 크기가 작지만 musl libc 사용으로 일부 Python 패키지 빌드에 문제가 생길 수 있습니다. 일반적으로 slim 변형이 크기와 호환성의 균형이 좋습니다.
목표
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 계정에 로그인하고, 로컬에서 빌드한 이미지를 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 이미지란?
docker build를 반복 실행하면 이전 빌드의 이미지가 태그 없이 남습니다. 이런 이미지를 Dangling 이미지라고 합니다.
# 첫 번째 빌드
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
# 전체 미사용 리소스 삭제
빌드 캐시 강제 삭제
안전한 실행 조건: 디스크 부족 대응 중이고 캐시를 재생성해도 되는 경우에만 실행합니다.
실행 전 반드시 확인
- 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 ECR | AWS 통합 | AWS 인프라 |
| Google Artifact Registry | GCP 통합 | GCP 인프라 |
| Harbor | 오픈소스 자체 호스팅 | 온프레미스 |
| GitLab Container Registry | GitLab 통합 | 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를 태그로 쓰면 배포 이력을 그대로 추적할 수 있습니다.
# 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에서는 이미지 이름의 전체 구성과 다이제스트로 불변성을 보장하는 방법을 다룹니다.

이미지 이름의 전체 형식
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 디스크 사용량을 파악하고 안전하게 정리하는 방법을 다룹니다.

디스크 사용량 파악
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 이미지가 생기는 이유
# 같은 태그로 재빌드하면 이전 이미지가 <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 등 핵심 명령어와 레이어 캐시 최적화를 통해 빠르고 작은 이미지를 만드는 기술을 다룹니다.