Skip to content
ioob.dev
Go back

Docker 9편 — 레지스트리, 이미지는 어디에 두는가

· 8분 읽기
Docker 시리즈 (9/13)
  1. Docker 입문 1편 — Docker란
  2. Docker 입문 2편 — 이미지와 레이어
  3. Docker 입문 3편 — Dockerfile 작성법
  4. Docker 입문 4편 — 컨테이너 생명주기
  5. Docker 입문 5편 — 볼륨과 데이터 영속성
  6. Docker 입문 6편 — 네트워크
  7. Docker 7편 — Docker Compose로 다중 컨테이너 오케스트레이션
  8. Docker 8편 — 멀티스테이지 빌드로 이미지 다이어트
  9. Docker 9편 — 레지스트리, 이미지는 어디에 두는가
  10. Docker 10편 — 컨테이너 보안, 터지기 전에 막는 것들
  11. Docker 11편 — BuildKit과 고급 빌드
  12. Docker 12편 — 프로덕션 모범 사례
  13. Docker 13편 — 트러블슈팅과 대안
Table of contents

Table of contents

레지스트리란 무엇인가

레지스트리는 이미지의 저장소다. 좀 더 정확히는, OCI(Open Container Initiative) 스펙을 따르는 이미지 서버다. Docker는 자체적으로 registry라는 오픈소스 구현체를 제공하고, Harbor나 Nexus는 그 위에 RBAC, 스캔, 복제 같은 기능을 덧댄 제품이다.

flowchart LR
    DEV["개발자 PC"] -->|docker push| REG["레지스트리"]
    CI["CI/CD"] -->|docker push| REG
    REG -->|docker pull| K8S["Kubernetes"]
    REG -->|docker pull| SERVER["서버 호스트"]
    REG -->|docker pull| DEV2["다른 개발자"]

이미지 하나는 여러 레이어로 구성되는데, 레지스트리는 이 레이어를 해시(digest) 단위로 관리한다. 같은 레이어를 다른 이미지가 공유하면 저장 공간을 절약할 수 있고, pull할 때도 이미 가진 레이어는 건너뛴다.

주요 레지스트리 비교

실무에서 자주 쓰는 것들을 정리해보자.

레지스트리운영 주체프라이빗 지원비용 감강점
Docker HubDocker Inc.유료 플랜적당공식 이미지 기본 저장소
AWS ECRAWS기본 프라이빗pull/push + 용량IAM 통합, VPC 엔드포인트
GCP GCR / Artifact RegistryGoogle기본 프라이빗용량 + 네트워크GKE 통합
Azure ACRAzure기본 프라이빗티어제AAD 통합
GitHub Container RegistryGitHub공개/비공개공개 무료GitHub Actions 통합
Harbor (셀프호스팅)CNCF기본 프라이빗인프라 비용RBAC, 스캔, 복제, 사인
Nexus / JFrog상용기본 프라이빗라이선스멀티 포맷(Maven, npm 등)

선택 기준은 크게 이렇다. 클라우드 네이티브 환경이면 각 클라우드의 레지스트리가 IAM 통합과 VPC 내부 pull 측면에서 이득이다. 온프레미스나 멀티 클라우드라면 Harbor가 가장 무난하다. 공개 오픈소스 배포라면 Docker Hub나 GHCR를 쓴다.

Docker Hub — 가장 흔한 출발점

개인 계정이든 조직 계정이든 가입하면 무료로 공개 리포를 만들 수 있다.

# 로그인
docker login

# 태깅 후 푸시
docker tag myapp:latest mydockerid/myapp:1.0.0
docker push mydockerid/myapp:1.0.0

첫 로그인 시 ~/.docker/config.json에 인증 토큰이 저장된다. CI 환경에서는 이 토큰 대신 Personal Access Token을 발급받아 사용하는 게 안전하다. 계정 비밀번호를 CI에 박아두는 건 금물이다.

# CI 환경에서 비대화형 로그인
echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin

Docker Hub는 pull 속도 제한이 있다. 인증된 사용자도 6시간당 200회, 익명은 100회. 대규모 클러스터가 앞에 공용 캐시 없이 이미지를 끌어당기다가 rate limit에 걸리는 일이 흔하다. 프로덕션 레지스트리로 Docker Hub만 쓰는 건 권장되지 않는다.

클라우드 제공자 레지스트리

AWS ECR

IAM으로 인증한다. aws ecr get-login-password로 받은 토큰을 docker login에 넘긴다.

aws ecr get-login-password --region ap-northeast-2 \
  | docker login --username AWS \
      --password-stdin 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com

docker tag myapp:latest 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:1.0.0
docker push 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:1.0.0

토큰은 12시간짜리다. EC2/EKS에서 IAM 인스턴스 프로파일을 쓰면 이 과정을 자동화할 수 있고, GitHub Actions에서는 aws-actions/configure-aws-credentials로 OIDC 페더레이션을 붙여 장기 키 없이 로그인한다.

GCP Artifact Registry

구버전 GCR는 점점 줄어들고 지금은 Artifact Registry가 공식이다.

gcloud auth configure-docker asia-northeast3-docker.pkg.dev

docker tag myapp:latest \
  asia-northeast3-docker.pkg.dev/my-project/my-repo/myapp:1.0.0
docker push asia-northeast3-docker.pkg.dev/my-project/my-repo/myapp:1.0.0

gcloud auth configure-docker는 현재 gcloud 인증을 사용하도록 Docker credential helper를 설정한다.

Azure ACR

az acr login --name myregistry
docker tag myapp:latest myregistry.azurecr.io/myapp:1.0.0
docker push myregistry.azurecr.io/myapp:1.0.0

AAD 토큰으로 인증한다. AKS에서는 az aks update --attach-acr로 클러스터와 ACR를 연결하면 imagePullSecret 없이 pull이 가능하다.

사내 레지스트리 — Harbor로 세우기

온프레 환경이거나 완전한 통제권이 필요하면 Harbor가 사실상의 표준이다. Kubernetes 위에 Helm으로 설치하는 게 가장 흔하다.

helm repo add harbor https://helm.goharbor.io
helm repo update

helm install harbor harbor/harbor \
  --namespace harbor --create-namespace \
  --set expose.type=ingress \
  --set expose.ingress.hosts.core=harbor.example.com \
  --set externalURL=https://harbor.example.com

Harbor는 이미지 저장 + RBAC + Trivy/Clair로 취약점 스캔 + 프로젝트 단위 격리 + 복제(replication)까지 한 번에 처리한다.

flowchart TB
    subgraph HARBOR["Harbor"]
      UI["Web UI / API"]
      REG2["Registry Backend"]
      SCAN["Vulnerability Scanner (Trivy)"]
      REPL["Replication Controller"]
      DB["Postgres / Redis"]
    end
    DEV["개발자 / CI"] -->|push / pull| UI
    UI --> REG2
    REG2 --> SCAN
    REG2 -.->|복제| REMOTE["원격 레지스트리"]
    UI --> DB

프로젝트를 만들고 개발자에게 역할(Guest/Developer/Maintainer)을 부여하는 구조라, 팀 단위로 접근 권한을 나누기 좋다. 푸시된 이미지를 자동 스캔하고, 심각도 임계치 이상이면 pull을 막는 정책도 설정 가능하다.

태깅 전략 — 가장 흔한 실수와 대안

초보자가 가장 자주 하는 실수는 :latest 하나로 모든 이미지를 덮어쓰는 것이다. latest는 태그가 아니라 일종의 “포인터”라서, 오늘의 latest와 어제의 latest가 전혀 다른 이미지일 수 있다. 프로덕션 장애 복구 때 어떤 이미지로 롤백해야 할지 알 수 없게 된다.

실무에서 쓰이는 태깅 방식은 크게 세 가지다.

1. semver — 릴리스 버전

사용자(다른 팀 또는 외부)가 명시적으로 버전을 선택해야 하는 이미지에 쓴다.

docker tag myapp:build harbor.example.com/team/myapp:1.4.2
docker tag myapp:build harbor.example.com/team/myapp:1.4
docker tag myapp:build harbor.example.com/team/myapp:1
docker tag myapp:build harbor.example.com/team/myapp:latest

1.4.2, 1.4, 1을 같이 태깅하면 사용자가 원하는 수준의 세분화로 pin할 수 있다. latest는 최신 안정판 포인터로만 쓴다.

2. git sha — 명확한 추적

내부 서비스 이미지에는 커밋 해시가 가장 실용적이다. 어떤 코드로부터 만들어진 이미지인지가 태그에 그대로 박힌다.

TAG=$(git rev-parse --short HEAD)   # 예: a1b2c3d
docker build -t harbor.example.com/team/myapp:$TAG .
docker push harbor.example.com/team/myapp:$TAG

Argo CD나 Flux 같은 GitOps 도구와 궁합이 좋다. git sha 태그는 불변이라 롤백도 간단하고, 장애 원인 추적할 때 커밋을 바로 찾을 수 있다.

3. 결합 — semver + git sha + 환경

가장 안정적인 건 목적별로 여러 태그를 조합하는 것이다.

VERSION=1.4.2
SHA=$(git rev-parse --short HEAD)
REG=harbor.example.com/team/myapp

docker build -t $REG:$VERSION -t $REG:$VERSION-$SHA -t $REG:$SHA .
docker push $REG:$VERSION
docker push $REG:$VERSION-$SHA
docker push $REG:$SHA

피해야 할 패턴

이미지 다이제스트로 pin하기

태그는 포인터지만, 다이제스트(sha256:...)는 불변이다. 정말 중요한 이미지(보안 베이스, 공용 라이브러리)는 다이제스트로 고정하는 게 안전하다.

docker pull alpine:3.20
docker inspect alpine:3.20 --format '{{index .RepoDigests 0}}'
# alpine@sha256:aabbcc...

Dockerfile에서 이렇게 고정할 수 있다.

FROM alpine@sha256:aabbccddeeff0011...

불편해 보이지만, 공급망 공격을 막는 확실한 방법이다. 공식 이미지 태그가 같은 이름으로 다른 콘텐츠로 교체되는 일이 과거에도 있었다.

멀티 아키텍처 이미지

같은 태그로 AMD64와 ARM64를 동시에 제공하고 싶을 때가 있다. 맥북이 Apple Silicon으로 많이 바뀌면서 일반적인 요구가 됐다.

docker buildx create --name multiarch --use
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t harbor.example.com/team/myapp:1.4.2 \
  --push .

buildx는 BuildKit의 진화형 명령이고, --platform으로 아키텍처 목록을 주면 각각 빌드해서 하나의 매니페스트로 묶어 푸시한다. pull 받는 쪽은 자기 아키텍처에 맞는 이미지를 자동으로 받는다. 자세한 내용은 11편에서 다룬다.

정리된 배포 흐름

CI에서 이미지를 빌드해서 푸시하고, 클러스터가 pull 받아 배포하는 전체 흐름을 그려보면 이렇다.

sequenceDiagram
    participant Dev as 개발자
    participant Git as Git
    participant CI as CI/CD
    participant Reg as 레지스트리
    participant K8s as Kubernetes

    Dev->>Git: git push
    Git->>CI: webhook
    CI->>CI: 빌드 + 테스트
    CI->>Reg: docker push myapp:v1.4.2-a1b2c3d
    CI->>Git: 매니페스트 이미지 태그 갱신
    Git->>K8s: ArgoCD 싱크
    K8s->>Reg: docker pull myapp:v1.4.2-a1b2c3d
    K8s->>K8s: 롤링 업데이트

이미지는 빌드되면 그 자체로 완성이 아니라, 레지스트리에 올라가야 “배포 가능한 산출물”이 된다. 어디에 둘지, 어떤 태그로 올릴지 정하는 건 단순한 작업이 아니라 운영 안정성의 큰 축이다.

여기까지의 상태

이미지를 외부에 공유할 수 있는 구조가 섰다. 다음 단계는 그 이미지를 어떻게 안전하게 만드느냐다.


다음 편에서는 컨테이너 보안을 다룬다. 비루트 실행, 이미지 스캔, 시크릿 관리, 읽기 전용 파일시스템까지 — 운영에서 터지기 전에 막아야 할 것들을 정리한다.

10편: 보안


Related Posts

Share this post on:

Comments

Loading comments...


Previous Post
Docker 8편 — 멀티스테이지 빌드로 이미지 다이어트
Next Post
Docker 10편 — 컨테이너 보안, 터지기 전에 막는 것들