빌드한 이미지는 어떻게 서버까지 가는가
이미지를 로컬에서 빌드하는 것과 그걸 실제 서버에서 실행하는 것 사이에는 한 단계가 더 있습니다. 이미지를 어딘가에 올려두고, 서버가 거기서 받아올 수 있어야 합니다. 그 "어딘가"가 레지스트리입니다.
레지스트리는 이미지를 위한 저장소입니다
Docker Hub가 가장 널리 알려진 공개 레지스트리입니다. docker pull nginx 를 실행할 때 어디서 이 이미지를 받아오는지 궁금했다면, 기본값이 바로 Docker Hub입니다. 회사에서는 보통 비공개 레지스트리(AWS ECR, GCP Artifact Registry, GitHub Container Registry, 또는 자체 호스팅하는 Harbor 등)를 씁니다. 원리는 다 같습니다.
태그 이름의 구조
[레지스트리 호스트]/[네임스페이스]/[이미지 이름]:[태그]docker tag my-app:latest ghcr.io/my-team/my-app:v1.2.0레지스트리 호스트를 생략하면(nginx:latest 처럼) Docker Hub를 가리키는 것으로 취급됩니다. 이 부분에서 흔한 실수가 하나 있습니다. 로컬에서 docker build -t my-app . 로 빌드한 이미지를 그대로 docker push my-app 하면, Docker Hub의 my-app 이라는 리포지토리로 올리려고 시도합니다. 사내 레지스트리에 올리고 싶었다면 먼저 그 레지스트리 주소가 포함된 이름으로 태그를 다시 붙여야 합니다.
빌드부터 배포까지의 흐름
sequenceDiagram participant Dev as 개발자 로컬 participant Reg as 레지스트리 participant Srv as 운영 서버 Dev->>Dev: docker build -t app:v1.2.0 . Dev->>Dev: docker tag app:v1.2.0 registry.example.com/app:v1.2.0 Dev->>Reg: docker push registry.example.com/app:v1.2.0 Srv->>Reg: docker pull registry.example.com/app:v1.2.0 Srv->>Srv: docker stop app (기존 컨테이너) Srv->>Srv: docker run -d --name app registry.example.com/app:v1.2.0
이 흐름에서 배포라는 행위는 결국 "새 이미지를 pull하고, 기존 컨테이너를 지운 뒤, 새 이미지로 컨테이너를 다시 만드는" 것입니다. 코드를 서버에 직접 올리거나 서버에서 빌드하지 않는다는 게 핵심입니다. 서버는 오직 이미 만들어진 이미지를 받아서 실행할 뿐이라, "서버마다 빌드 결과가 미묘하게 다르다"는 문제 자체가 발생할 여지가 없습니다.
latest 태그를 프로덕션에서 쓰면 안 되는 이유
docker run -d my-app:latestlatest 는 그냥 관례적인 이름일 뿐, Docker가 "가장 최신 버전"이라고 특별 취급하는 태그가 아닙니다. 태그를 안 붙이면 자동으로 latest 가 붙을 뿐입니다. 문제는 이겁니다. 오늘 latest 로 배포한 이미지와 다음 주에 latest 로 다시 빌드해서 배포한 이미지가 완전히 다른 코드일 수 있는데, 태그만 봐서는 구분이 안 됩니다. 문제가 생겨서 "지난 배포로 롤백"하고 싶어도 latest 라는 이름만으로는 어느 버전이 지난 배포였는지 알 수 없습니다.
실무에서는 커밋 해시나 시맨틱 버전을 태그로 씁니다.
docker tag my-app registry.example.com/app:$(git rev-parse --short HEAD)
docker tag my-app registry.example.com/app:v1.2.0이렇게 하면 "어떤 배포가 어떤 커밋에서 나왔는지"가 태그 이름에 그대로 남고, 문제가 생겼을 때 이전 태그로 다시 docker run 하는 것만으로 즉시 롤백할 수 있습니다.
이 흐름을 손으로 하지 않는 이유
앞서 설명한 pull → stop → run 과정을 매번 서버에 SSH로 접속해서 손으로 하는 팀은 이제 거의 없습니다. GitHub Actions 같은 CI 파이프라인이 코드 푸시를 감지해서 빌드·푸시까지 자동으로 하고, 서버 쪽 배포도 오케스트레이션 도구(다음 장에서 다룰 내용)나 최소한 배포 스크립트로 자동화합니다. 다만 그 자동화된 파이프라인 내부에서 실제로 벌어지는 일은 지금까지 설명한 build → tag → push → pull → run 그대로입니다. 이 기본 흐름을 이해하고 있어야, 파이프라인이 실패했을 때 어느 단계에서 막혔는지 짐작할 수 있습니다.