홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

Docker :latest로 재배포했는데 옛 이미지가 뜨는 이유

정기창·2026년 7월 11일

앞 글에서 arm64 이미지를 무사히 빌드해 레지스트리에 올렸습니다. 이제 서버가 그 이미지를 받아 실행할 차례입니다. 그런데 새 이미지를 올리고 서버를 재배포했는데도, 화면은 계속 옛 버전 그대로였습니다.

증상: 분명히 재배포했는데 안 바뀐다

CI에서 새 이미지를 빌드해 :latest 태그로 레지스트리에 올렸고, 서버에서 재배포 버튼도 눌렀습니다. 컨테이너는 새로 떴는데, 내려오는 번들은 옛것이었습니다. 재배포가 이미지를 새로 받아오지 않은 것입니다.

원인: 태그는 움직여도, 로컬 캐시는 안 움직인다

핵심은 :latest가 특정 이미지를 가리키는 이름표일 뿐이라는 데 있습니다. 새 이미지를 push하면 레지스트리 쪽의 :latest는 새 다이제스트(내용 해시)를 가리키도록 옮겨갑니다. 하지만 서버의 도커는 이미 로컬에 :latest라는 이름의 이미지를 갖고 있으면, 그게 최신인지 확인하지 않고 그대로 재사용합니다.

docker compose up 같은 명령도 마찬가지입니다. compose 파일의 이미지 스펙(image: ...:latest)이 문자열로는 그대로이니, 도커는 "바뀐 게 없다"고 판단해 컨테이너를 재생성하지 않습니다. 태그는 레지스트리에서 움직였지만, 서버는 그 사실을 모르는 셈입니다.

확인: 다이제스트를 비교한다

정말 로컬이 옛 이미지를 붙잡고 있는지는 다이제스트로 확인할 수 있습니다.

# 로컬 :latest 다이제스트
docker images --digests | grep app-frontend

# 강제로 최신을 당긴 뒤 다시 보면 다이제스트가 바뀐다
docker pull ghcr.io/OWNER/app-frontend:latest
docker images --digests | grep app-frontend

docker pull 전후로 다이제스트가 달라진다면, 그동안 로컬이 옛 이미지를 쓰고 있었다는 증거입니다.

해결: 세 가지 방법

방법 내용 비고
명시적 pull 재배포 전 docker pull로 로컬 :latest를 갱신 즉효, 수동
항상 pull 배포 시 docker compose pull 또는 pull_policy: always 자동, 매번 조회
sha 태그 핀 :latest 대신 커밋 sha 등 불변 태그를 참조 근본책, 롤백 명확

가장 확실한 건 불변 태그입니다. 이미지를 :latest와 함께 sha-abc1234 같은 커밋 단위 태그로도 올려 두고, 배포 스펙이 그 sha를 가리키게 하면, 배포마다 참조가 실제로 달라지므로 캐싱 문제가 원천적으로 사라집니다. 어떤 커밋이 떠 있는지 명확해져서 롤백도 쉬워집니다.

self-hosted PaaS에서의 함정

Coolify 같은 self-hosted PaaS를 쓴다면 한 가지 더 주의할 점이 있습니다. 재배포나 웹훅 트리거가 컨테이너를 재시작만 하고 이미지를 새로 받지 않는 경우가 있습니다. 강제 옵션을 줘도 pull을 건너뛰기도 합니다(알려진 이슈입니다). 이럴 때는 "최신 이미지를 받아 재시작" 같은 전용 동작을 쓰거나, 서버에서 직접 docker pull한 뒤 재배포해야 합니다.

정리하며

:latest는 쓰기 편하지만, "재배포하면 최신이 뜬다"를 보장해 주지는 않습니다. 편의의 이면에는 로컬 캐시라는 눈에 안 보이는 상태가 있고, 배포가 조용히 옛 버전을 붙잡는 사고는 대개 여기서 나옵니다.

자동 배포까지 염두에 둔다면 sha 같은 불변 태그로 핀하는 편이 낫겠다는 결론에 이르렀습니다. 수동 배포라면 최소한 배포 절차에 "최신을 확실히 받는" 단계를 넣어 두는 것이, 옛 화면을 붙잡고 원인을 찾아 헤매는 시간을 줄여 줍니다.

이렇게 이미지를 빌드하고, 서버가 받아 실행하는 데까지 왔습니다. 다음 글에서는 이 새 서버로 실제 도메인을 넘기는 무중단 컷오버와, 거기서 만난 인증서 함정을 다룹니다.

Docker배포CI/CD컨테이너GHCR

관련 글

빌드는 CI에서, 실행은 서버에서: GHCR pull-only 배포

작은 서버는 앱을 실행할 여유는 있어도 빌드에서 죽습니다. 빌드를 GitHub Actions로 빼고 서버는 완성 이미지를 받아 실행만 하는 pull-only 구조와, private 레지스트리 인증까지 정리했습니다.

관련도 92%

OCI ARM64 서버 통합기: 빌드 RAM 병목을 GitHub Actions로 분리했습니다

무료 OCI ARM64 서버 2대를 1대로 합치려다, 진짜 병목이 런타임이 아니라 '빌드 RAM 피크'라는 걸 알게 됐습니다. 박스에서 빌드를 걷어내고 GitHub Actions 네이티브 arm64 러너로 빌드해 GHCR에 올리는 '빌드/실행 분리'로 통합을 해낸 회고입니다.

관련도 89%

삭제한 기능이 남긴 참조가 새 빌드를 죽였습니다

서버를 옮기며 오래된 코드를 새로 빌드하자, 잘 돌던 화면이 첫 렌더부터 하얗게 죽었습니다. 5개월 동안 배포하지 않은 main에 숨어 있던, 삭제된 기능이 남긴 dangling 참조를 추적한 기록입니다.

관련도 88%