[도커 30강] 28강. 레지스트리 푸시/풀 전략과 캐시 활용
컨테이너를 잘 만드는 팀과 운영을 잘하는 팀의 차이는 Dockerfile 문법 숙련도보다 이미지 유통 전략에서 더 크게 벌어집니다. 로컬에서만 docker build가 빠른 것은 큰 의미가 없습니다. 실무에서는 CI 러너, 스테이징 서버, 운영 노드가 서로 다른 환경에서 이미지를 가져가며, 이때 태그 정책이 불명확하거나 캐시 전략이 엉키면 배포 속도와 안정성이 동시에 무너집니다.
이번 강의에서는 레지스트리에 이미지를 어떻게 푸시/풀해야 추적 가능성과 배포 안전성을 동시에 확보할 수 있는지, 그리고 빌드 캐시를 어디까지 신뢰하고 어디서 강제 무효화해야 하는지를 실무 관점으로 정리하겠습니다. 특히 “빠르게”와 “재현 가능하게”는 종종 충돌하므로, 둘 사이 균형을 잡는 기준까지 함께 설명합니다.
핵심 개념
- 레지스트리 전략의 핵심은 태그를 사람이 읽기 쉽게 만드는 것이 아니라, 문제 발생 시 정확히 되돌릴 수 있게 만드는 것입니다.
latest는 편의 태그일 뿐 기준 태그가 아닙니다. 기준은 항상 **불변 태그(커밋 SHA, 빌드 번호, 릴리스 버전)**여야 합니다.- Pull 정책은 환경별로 다릅니다. 개발 환경은 빠른 반복을 위해 캐시 활용 비중이 크고, 운영 환경은 정합성을 위해 digest 고정과 pull 검증이 중요합니다.
- 빌드 캐시는 시간을 절약해주지만, 의존성 변경/베이스 이미지 보안 업데이트/빌드 인자 변경처럼 반드시 캐시를 버려야 할 순간이 있습니다.
- 레지스트리 접근 제어(권한, 네임스페이스, 만료 정책)는 보안 이슈이면서 운영 품질 이슈입니다. 태그 난립은 곧 장애 대응 속도 저하로 이어집니다.
기본 사용
예제 1) 불변 태그 + 편의 태그 동시 푸시
IMAGE=ghcr.io/acme/myapp
SHA_TAG=$(git rev-parse --short=12 HEAD)
VERSION_TAG=v1.4.0
# 빌드
docker build -t ${IMAGE}:${SHA_TAG} -t ${IMAGE}:${VERSION_TAG} -t ${IMAGE}:latest .
# 푸시
docker push ${IMAGE}:${SHA_TAG}
docker push ${IMAGE}:${VERSION_TAG}
docker push ${IMAGE}:latest
설명:
- 최소 2종류 태그를 함께 운영하세요: 불변 태그(SHA) + 가독성 태그(버전, latest).
- 장애 대응 시에는 반드시 SHA 또는 digest 기준으로 롤백해야 합니다.
- 팀 규칙 문서에 “운영 배포는 latest 금지”를 명시해두면 사고를 크게 줄일 수 있습니다.
예제 2) Digest 기반으로 정확한 이미지 풀 고정
IMAGE=ghcr.io/acme/myapp
# 먼저 digest 확인
DIGEST=$(docker buildx imagetools inspect ${IMAGE}:v1.4.0 --format '{{json .Manifest.Digest}}' | tr -d '"')
# 운영에서는 digest pinning 권장
docker pull ${IMAGE}@${DIGEST}
docker run -d --name myapp-prod ${IMAGE}@${DIGEST}
docker ps --filter name=myapp-prod --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
설명:
- 같은 태그라도 시간이 지나면 가리키는 이미지가 바뀔 수 있습니다. digest는 내용 기반 고유 식별자라 불변입니다.
- 운영 배포 기록에 “태그 + digest”를 함께 남기면, 나중에 이미지 위변조/오배포 의심 상황에서 추적이 빨라집니다.
- 특히 여러 리전을 운영할 때 digest 고정은 환경 간 동일성 보장에 매우 효과적입니다.
예제 3) BuildKit 캐시를 레지스트리 캐시와 함께 사용
IMAGE=ghcr.io/acme/myapp
CACHE_REF=ghcr.io/acme/myapp:buildcache
SHA_TAG=$(git rev-parse --short=12 HEAD)
docker buildx build \
--platform linux/amd64 \
-t ${IMAGE}:${SHA_TAG} \
--cache-from type=registry,ref=${CACHE_REF} \
--cache-to type=registry,ref=${CACHE_REF},mode=max \
--push \
.
설명:
- CI 러너가 매번 새로 뜨는 환경이라면 로컬 캐시보다 레지스트리 캐시가 효과적입니다.
mode=max는 캐시 범위를 넓혀 재사용성을 높이지만 저장소 용량 증가를 동반합니다.- 캐시 레퍼런스(
buildcache)는 애플리케이션 이미지와 분리해 관리하면, 운영 이미지 정리와 캐시 정리를 독립적으로 할 수 있습니다.
예제 4) 캐시 무효화 기준을 스크립트로 고정
#!/usr/bin/env bash
set -euo pipefail
IMAGE=ghcr.io/acme/myapp
SHA_TAG=$(git rev-parse --short=12 HEAD)
FORCE_NO_CACHE=${FORCE_NO_CACHE:-false}
if [ "$FORCE_NO_CACHE" = "true" ]; then
echo "[build] no-cache mode"
docker build --no-cache -t ${IMAGE}:${SHA_TAG} .
else
echo "[build] cache mode"
docker build -t ${IMAGE}:${SHA_TAG} .
fi
# 베이스 이미지 보안패치 주기 반영을 위해 pull 동기화
docker build --pull -t ${IMAGE}:${SHA_TAG}-pullcheck .
설명:
- 캐시는 기본값으로 사용하되, 강제 무효화 경로를 항상 열어두세요.
--pull을 정기적으로 실행하면 베이스 이미지 업데이트를 놓치지 않을 수 있습니다.- 보안 패치 배포 주기(예: 주 1회)는 캐시 전략과 별개로 정책화하는 것이 좋습니다.
자주 하는 실수
실수 1) latest만 보고 배포 대상 식별
- 원인: 태그 관리 비용을 줄이려다 추적성까지 포기함.
- 해결: 운영 배포 파이프라인 입력값을 SHA 또는 digest로 제한하세요. latest는 개발 편의용으로만 사용하세요.
실수 2) CI 캐시가 있으니 항상 최신일 거라고 가정
- 원인: 캐시 히트율만 지표로 보고 정합성 검증을 생략함.
- 해결: 주기적으로
--no-cache빌드를 수행하고 결과 이미지 차이를 점검하세요.
실수 3) 동일 태그를 여러 브랜치에서 재사용
- 원인: 브랜치별 네이밍 규칙 부재 (
dev,test,prod혼용). - 해결: 네임스페이스/태그 규칙을 분리하세요. 예:
acme/myapp-dev,acme/myapp-prod또는branch-SHA패턴.
실수 4) Pull 실패/지연 상황을 고려하지 않은 배포 스크립트
- 원인: 레지스트리 네트워크 장애를 정상 시나리오로 가정하지 않음.
- 해결: pull 재시도, 타임아웃, 직전 정상 digest fallback 전략을 배포 자동화에 포함하세요.
실무 패턴
현업에서 안정적인 레지스트리 운영은 “이미지 저장”이 아니라 “릴리스 이력 관리”에 가깝습니다. 즉, 어떤 커밋이 어떤 이미지로 빌드되었고, 그 이미지가 어느 환경에 언제 배포되었는지를 일관되게 남겨야 합니다. 이 기록이 있어야 장애 시 원인 분석이 빨라지고, 컴플라이언스 요구가 생겼을 때도 증적을 제시할 수 있습니다.
가장 추천하는 패턴은 다음과 같습니다. 첫째, CI 빌드 단계에서 SHA 태그를 항상 발행하고, 둘째, main 브랜치 또는 릴리스 태그에서만 사람이 읽기 쉬운 태그(v1.4.0, stable)를 추가합니다. 셋째, CD는 SHA/digest 입력만 받아 배포합니다. 이렇게 하면 “보기 좋은 태그”와 “안전한 배포 식별자”를 분리할 수 있습니다.
캐시 전략은 성능 최적화와 재현성 사이의 균형 문제입니다. 팀이 성장할수록 빌드 시간 절감 압박이 커지는데, 이때 캐시 히트율만 쫓으면 보안 패치 누락이나 의존성 꼬임이 발생할 수 있습니다. 따라서 캐시 정책에는 반드시 “강제 갱신 시점”이 들어가야 합니다. 예를 들면, 주간 보안 점검 빌드는 --pull --no-cache로 실행하고, 평시 PR 빌드는 레지스트리 캐시를 적극 활용하는 식입니다.
또한 이미지 수명주기 정책도 매우 중요합니다. 태그를 무제한으로 쌓아두면 비용 문제를 넘어 운영자가 필요한 이미지를 찾기 어려워집니다. 보통은 “최근 N개 릴리스 태그 유지 + 오래된 PR 태그 자동 정리 + buildcache 별도 정리 정책”을 함께 둡니다. 이 정책이 정리되어 있으면 저장소 비용과 장애 대응 속도 모두 개선됩니다.
마지막으로, 사람 실수를 줄이는 장치가 필요합니다. 배포 승인 화면이나 챗옵스 메시지에 “배포 대상: repo@digest, 생성 커밋, 테스트 통과 여부”를 함께 노출하세요. 운영자가 latest 문자열만 보고 승인하는 구조는 반드시 사고가 납니다. 확인해야 할 정보를 강제로 보이게 만드는 것이 실무 자동화의 핵심입니다.
오늘의 결론
한 줄 요약: 레지스트리 실무의 정답은 latest 중심 운영이 아니라, SHA/digest 기반 추적성과 캐시 무효화 규칙을 함께 고정하는 것이다.
연습문제
- 현재 프로젝트 CI에서 이미지 태그를
latest단독에서SHA + latest병행으로 바꾸고, 배포 스크립트 입력은 SHA만 받도록 수정해보세요. - 운영 배포 로그에
image digest,git commit,배포 시각을 남기고, 임의 장애 상황에서 직전 digest로 롤백하는 절차를 문서화해보세요. - 주간 1회
--pull --no-cache빌드 잡을 추가해 캐시 기반 빌드 결과와 차이가 나는지 점검 리포트를 작성해보세요.
이전 강의 정답
27강 연습문제 해설:
docker-compose.ci.yml기반 통합 테스트는 앱 컨테이너와 DB 컨테이너를 함께 띄우고,--exit-code-from app으로 테스트 결과를 잡 상태에 반영하는 방식이 핵심입니다.- 태그를
latest + SHA로 운영할 때 배포 스크립트는 반드시 SHA를 기본 입력으로 받아야 하며, latest는 개발 편의 또는 빠른 확인 목적에만 사용해야 합니다. - CI 로그 가독성 개선은 “단계 구분 출력 + 실패 시 정리(always down -v) + 원인 파악 가능한 에러 메시지” 3요소를 갖추면 충분히 실전에서 통합니다.
실습 환경/재현 정보
- OS: macOS Sonoma/Sequoia 또는 Ubuntu 22.04 이상
- Docker 버전: Docker Engine 24+ / 25+, Docker Compose v2, buildx 사용 가능 환경
- 실행 순서:
- 레지스트리 로그인(
docker login또는 CI OIDC 로그인) 설정 - SHA 태그 기반 빌드/푸시 스크립트 작성
- 레지스트리 캐시(
cache-from/cache-to) 적용 - 배포 단계에서 digest 확인 및 고정 실행
- 주기적 no-cache 검증 빌드 추가
- 레지스트리 로그인(
- 재현 체크:
- 동일 커밋에서 재빌드 시 예상 범위 내 캐시 히트가 발생하는가?
- 운영 배포 기록에서 이미지 digest를 즉시 확인할 수 있는가?
- latest 변경과 무관하게 SHA/digest 기준 롤백이 가능한가?
- 레지스트리 장애 시 pull 재시도/timeout/fallback이 동작하는가?
- 오래된 태그/캐시 정리 정책이 자동화되어 있는가?