이미지 태그와 레지스트리 — Docker Hub에서 프라이빗 레지스트리까지
docker pull nginx를 실행하면 어떤 버전이 받아질까요? 오늘과 내일 받아지는 이미지가 같을 거라는 보장이 있을까요?
이미지 이름의 구조
Docker 이미지의 전체 이름(reference)은 다음과 같은 구조입니다.
[레지스트리 호스트/][네임스페이스/]이미지이름[:태그|@다이제스트]
# Docker Hub 공식 이미지 (라이브러리 네임스페이스)
nginx:1.25-alpine
# 실제 전체 경로: docker.io/library/nginx:1.25-alpine
# Docker Hub 사용자 이미지
myuser/myapp:v1.0.0
# 실제 전체 경로: docker.io/myuser/myapp:v1.0.0
# GitHub Container Registry
ghcr.io/myorg/myapp:latest
# AWS ECR
123456789.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v2.1.0
# 다이제스트로 참조
nginx@sha256:abc123def456...
태그의 본질
태그는 이미지에 붙이는 가변(mutable) 라벨입니다. 같은 태그가 다른 이미지를 가리킬 수 있다는 점이 중요합니다.
# 같은 이미지에 여러 태그 부여
docker tag myapp:latest myapp:v1.0.0
docker tag myapp:latest myapp:stable
# 이후 새 빌드에서 latest를 덮어쓸 수 있음
docker build -t myapp:latest .
# 이제 latest는 새 이미지를 가리키지만, v1.0.0은 이전 이미지를 가리킴
latest의 함정
latest는 특별한 태그가 아닙니다. 단순히 태그를 명시하지 않았을 때의 기본값일 뿐입니다.
- 자동으로 최신 버전을 가리키지 않습니다
- 누군가
docker push myapp:latest를 하면 덮어씌워집니다 - 어떤 버전인지 추적할 수 없습니다
# 이 두 명령어는 동일
docker pull nginx
docker pull nginx:latest
# 하지만 오늘의 nginx:latest와 내일의 nginx:latest가
# 같은 이미지라는 보장은 없습니다
태그 전략
Semantic Versioning (SemVer)
myapp:1.2.3 # 정확한 버전 (patch)
myapp:1.2 # 마이너 버전 (1.2.x 중 최신)
myapp:1 # 메이저 버전 (1.x.x 중 최신)
myapp:latest # 최신 안정 버전
공식 이미지들이 흔히 사용하는 패턴입니다.
node:20.12.2 # 정확한 버전
node:20.12 # 패치 업데이트 자동
node:20 # 마이너 업데이트 자동
node:20-alpine # 변형(variant) 포함
node:20.12.2-alpine3.19 # 가장 구체적인 태그
Git SHA 태그
myapp:a1b2c3d # Git 커밋 해시 (짧은 형태)
myapp:main-a1b2c3d # 브랜치 + 커밋 해시
장점: 코드와 이미지의 1:1 매핑이 가능하여 추적이 쉽습니다.
# CI에서 자동 태깅 예시
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t myapp:${GIT_SHA} -t myapp:latest .
날짜 기반 태그
myapp:20260319 # 빌드 날짜
myapp:20260319-a1b2c3d # 날짜 + Git SHA
추천 전략: 복합 태깅
# 하나의 빌드에 여러 태그를 부여
docker build \
-t myapp:v1.2.3 \
-t myapp:v1.2 \
-t myapp:v1 \
-t myapp:latest \
-t myapp:$(git rev-parse --short HEAD) \
.
- 프로덕션 배포:
v1.2.3(정확한 버전) - 디버깅/추적:
a1b2c3d(Git SHA) - 개발 환경:
latest - 롤백: 이전 SemVer 태그로 빠르게 전환
다이제스트 — 불변의 식별자
태그와 달리 다이제스트(digest)는 이미지 내용의 SHA256 해시입니다. 내용이 바뀌면 다이제스트도 바뀝니다.
# 이미지의 다이제스트 확인
docker inspect --format='{{index .RepoDigests 0}}' nginx:1.25-alpine
# nginx@sha256:a1b2c3d4e5f6...
# 다이제스트로 pull (100% 재현 가능)
docker pull nginx@sha256:a1b2c3d4e5f6...
프로덕션에서 정확한 재현성이 필요할 때 다이제스트를 사용합니다.
# 태그 대신 다이제스트 사용 (가장 안전)
FROM nginx@sha256:a1b2c3d4e5f6...
레지스트리 비교
Docker Hub
# 로그인
docker login
# 이미지 push
docker tag myapp:v1.0.0 myuser/myapp:v1.0.0
docker push myuser/myapp:v1.0.0
- 가장 범용적인 레지스트리
- 무료 계정: 1개의 프라이빗 리포지토리
- 공식 이미지의 기본 소스
- Rate limit: 익명 100회/6시간, 인증 200회/6시간
GitHub Container Registry (GHCR)
# GitHub PAT로 로그인
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
# push
docker tag myapp:v1.0.0 ghcr.io/myorg/myapp:v1.0.0
docker push ghcr.io/myorg/myapp:v1.0.0
- GitHub 계정과 통합
- GitHub Actions에서 자동 인증 (
GITHUB_TOKEN) - 리포지토리 권한과 연동 가능
- 퍼블릭 이미지 무료, 프라이빗도 넉넉한 무료 용량
AWS ECR (Elastic Container Registry)
# AWS CLI로 로그인 (토큰 12시간 유효)
aws ecr get-login-password --region ap-northeast-2 | \
docker login --username AWS --password-stdin \
123456789.dkr.ecr.ap-northeast-2.amazonaws.com
# 리포지토리 생성 (콘솔 또는 CLI)
aws ecr create-repository --repository-name myapp
# push
docker push 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1.0.0
- AWS 서비스(ECS, EKS)와 긴밀한 통합
- IAM 기반 접근 제어
- 이미지 스캐닝 내장
- 수명 주기 정책으로 자동 정리
Harbor (자체 호스팅)
# Harbor 설치 (docker-compose 기반)
# harbor.mydomain.com 같은 도메인으로 접근
docker login harbor.mydomain.com
docker push harbor.mydomain.com/myproject/myapp:v1.0.0
- CNCF 졸업 프로젝트
- 자체 인프라에서 운영
- 취약점 스캐닝(Trivy 내장), RBAC, 감사 로그
- 이미지 복제(replication) 기능
비교 표
| 특성 | Docker Hub | GHCR | ECR | Harbor |
|---|---|---|---|---|
| 호스팅 | 클라우드 | 클라우드 | 클라우드 | 자체 호스팅 |
| 인증 | Docker ID | GitHub Token | IAM | LDAP/OIDC |
| 비용 | 무료/유료 | 무료/유료 | 종량제 | 무료 (인프라 비용) |
| 스캐닝 | Docker Scout | 없음 | 내장 | Trivy 내장 |
| CI 통합 | 범용 | GitHub Actions | CodePipeline | 범용 |
이미지 관리 실무
태그 불변성(Immutable Tags)
일부 레지스트리는 태그 덮어쓰기를 방지하는 기능을 제공합니다.
# ECR: 태그 불변성 활성화
aws ecr put-image-tag-mutability \
--repository-name myapp \
--image-tag-mutability IMMUTABLE
이렇게 설정하면 v1.0.0 태그를 한 번 push한 후 같은 태그로 다시 push할 수 없습니다.
이미지 정리 정책
오래된 이미지가 쌓이면 저장소 비용이 증가합니다.
# ECR 수명 주기 정책 예시
aws ecr put-lifecycle-policy --repository-name myapp --lifecycle-policy-text '{
"rules": [
{
"rulePriority": 1,
"description": "30일 이상 된 untagged 이미지 삭제",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 30
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "최근 10개 태그만 유지",
"selection": {
"tagStatus": "tagged",
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": { "type": "expire" }
}
]
}'
미러링 및 프록시
# Docker Hub Rate Limit 우회를 위한 미러 설정
# /etc/docker/daemon.json
{
"registry-mirrors": ["https://mirror.gcr.io"]
}
Harbor에서는 프록시 캐시 기능으로 Docker Hub 이미지를 자동으로 캐싱할 수 있습니다.
멀티 아키텍처 이미지
ARM(Apple Silicon, AWS Graviton)과 AMD64를 모두 지원하는 이미지를 만들 수 있습니다.
# buildx로 멀티 아키텍처 빌드 + push
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myregistry.com/myapp:v1.0.0 \
--push .
# 매니페스트 확인
docker manifest inspect nginx:1.25-alpine
정리
- 이미지 태그는 가변이므로 프로덕션에서는 정확한 버전 태그(
v1.2.3)나 다이제스트를 사용합니다. latest는 개발 편의를 위한 것이지, 프로덕션 배포용이 아닙니다.- Git SHA 태그를 함께 사용하면 코드와 이미지를 정확히 추적할 수 있습니다.
- 레지스트리는 팀의 인프라 환경에 맞게 선택합니다. GitHub 중심이면 GHCR, AWS 중심이면 ECR, 자체 관리가 필요하면 Harbor가 적합합니다.
- 수명 주기 정책을 설정하여 오래된 이미지를 자동 정리하는 것을 잊지 마세요.
댓글 로딩 중...