docker pull myapp:latest로 배포를 굴리다 보면, 어제 잘 돌던 서비스가 오늘 재배포에서 갑자기 깨지는 일이 생깁니다. latest는 "최신"이라는 뜻의 고정된 라벨일 뿐, 같은 태그가 시점마다 다른 이미지를 가리킬 수 있기 때문입니다. 운영에서 latest를 쓰면 "지금 무슨 버전이 돌고 있는지"를 아무도 확신할 수 없게 됩니다.
latest가 일으키는 실제 문제
| 문제 | 왜 생기나 |
|---|---|
| 노드마다 다른 버전 | 풀 시점이 달라 같은 latest가 서로 다른 이미지 |
| 롤백 불가 | "이전 latest"라는 건 존재하지 않음 |
| 캐시 혼란 | imagePullPolicy 기본 동작이 태그에 따라 달라짐 |
| 재현 불가 | 빌드 로그에 latest만 남아 사후 추적 어려움 |
특히 쿠버네티스에서 태그가 latest면 imagePullPolicy가 자동으로 Always가 되고, 그 외 태그면 IfNotPresent가 기본이라 동작 자체가 달라집니다.
1단계 — 불변(immutable) 태그 쓰기
한 번 푸시한 태그는 다시 덮어쓰지 않는 것이 원칙입니다. 커밋 SHA나 CI 빌드 번호를 태그로 박습니다.
docker build -t myapp:1.4.2 -t myapp:$(git rev-parse --short HEAD) .
docker push myapp:1.4.2
docker push myapp:$(git rev-parse --short HEAD)
myapp:a1b2c3d처럼 커밋에 1:1로 묶이면, 장애 때 "어느 커밋이 돌고 있었나"가 즉시 드러납니다.
2단계 — 시맨틱 버전 + 이동 태그 분리
사람이 읽을 버전(1.4.2)과 편의용 이동 태그(1.4, 1)를 함께 둡니다.
| 태그 | 가리키는 것 | 용도 |
|---|---|---|
1.4.2 | 정확히 그 빌드 (불변) | 배포·롤백 기준 |
1.4 | 1.4.x 중 최신 | 패치 자동 수용 |
1 | 1.x 중 최신 | 메이저 호환 |
운영 배포에는 항상 1.4.2처럼 완전히 고정된 태그만 지정합니다.
3단계 — 정말 고정하려면 다이제스트
태그조차 덮어쓰일 수 있는 환경이라면, 변하지 않는 콘텐츠 해시(다이제스트)로 핀합니다.
docker pull myapp@sha256:9f86d08...e3b0c44
# kubernetes
image: myapp@sha256:9f86d08...e3b0c44
다이제스트는 이미지 내용이 1바이트만 달라져도 바뀌므로, "정확히 이 이미지"를 100% 보장합니다.
확인·검증
docker images --digests myapp # 태그별 다이제스트 비교
docker inspect myapp:1.4.2 --format '{{.RepoDigests}}'
같은 태그인데 노드마다 다이제스트가 다르면, 이미 latest류의 가변 태그 문제가 발생한 것입니다.
적용 체크리스트
# 운영 매니페스트에 latest가 남아있는지 점검
grep -rn ":latest" k8s/ docker-compose*.yml
docker images --digests # 태그=다이제스트 1:1 확인
latest를 운영에서 추방하고 불변 태그로 배포하면, 롤백은 "이전 태그로 되돌리기" 한 줄이면 끝납니다.
태그를 일부러 덮어써 배포 불일치를 만들어 보고 다이제스트로 고정하는 실습은 도커 트랙에서 무료로 해볼 수 있습니다.