본문 바로가기
클라우드

docker pull nginx 한 줄에 숨은 비밀 — 도메인을 생략하면 어디로 가는가? 🐳

by gasbugs 2026. 4. 18.

"왜 nginx만 쳤는데 이미지가 받아져요?"
— 입문자가 가장 먼저 궁금해하는 이 질문 한 줄에,
Docker 레지스트리의 핵심 철학이 전부 담겨 있다.

🎯 이 글에서 다루는 것

  • Docker 이미지 참조(reference)의 전체 포맷과 생략 규칙
  • nginx → docker.io/library/nginx:latest로 확장되는 3단계 메커니즘
  • 순정 Docker Engine에서 기본 레지스트리를 바꿀 수 있는지 (스포일러: 없다)
  • registry-mirrors, Podman의 unqualified-search-registries로 우회하는 실전 방법
  • 짧은 이름(short name)이 부르는 보안 위협 — typosquatting

📌 왜 이 주제가 중요한가

하루에도 수십 번 치는 docker pull nginx. 너무 자연스러워서 한 번도 의심해본 적이 없을 것이다. 하지만 이 한 줄은 사실 세 번의 자동 보정을 거친 결과다. 이 보정 규칙을 모르면 다음과 같은 상황에서 곧바로 막힌다.

  • 사내 프라이빗 레지스트리로 이미지 출처를 일원화해야 할 때
  • 중국·중동 등 Docker Hub 접속이 제한된 환경에서 배포해야 할 때
  • Docker Hub의 pull rate limit (익명 사용자 시간당 100회)을 회피해야 할 때
  • Typosquatting 공격(유사 이름 악성 이미지)을 방어해야 할 때

즉, 이 메커니즘은 단순한 "편의 기능"이 아니라, 운영·보안·비용이 얽힌 중요한 설계 포인트다.

🔍 이미지 참조의 완전한 포맷

Docker 이미지 참조는 다음 구조를 따른다.

[REGISTRY[:PORT]/]NAMESPACE/REPOSITORY[:TAG|@DIGEST]

 

구성 요소 설명 생략시 기본값
REGISTRY 이미지가 저장된 레지스트리 도메인 docker.io
NAMESPACE 조직·유저·특수 네임스페이스 library (docker.io 한정)
REPOSITORY 이미지 저장소 이름 (생략 불가)
TAG 버전·변형 식별자 latest

 

그래서 docker pull nginx 한 줄을 Docker 데몬이 내부적으로 처리할 때는, 아래와 같이 세 단계의 자동 보정이 차례대로 일어난다.

nginx
  ↓ (1) 레지스트리 생략 → docker.io 붙임
docker.io/nginx
  ↓ (2) 네임스페이스 생략 → library 붙임
docker.io/library/nginx
  ↓ (3) 태그 생략 → latest 붙임
docker.io/library/nginx:latest

 

실제로 docker pull nginx를 실행해 보면 마지막 줄에 docker.io/library/nginx:latest가 찍히는 것을 볼 수 있다. Docker가 "내가 사실 이렇게 해석했어요"라고 친절히 알려주는 셈이다.

library 네임스페이스의 정체

여기서 한 가지 오해가 있다. library라는 네임스페이스는 docker.io 레지스트리 전용 특수 규칙이지, 범용 규약이 아니다. 즉, Quay.io나 GCR 같은 다른 레지스트리에서는 이 규칙이 적용되지 않는다.

# docker.io 에서만 library 자동 보정이 일어난다
docker pull nginx                       # ✅ docker.io/library/nginx:latest
docker pull quay.io/nginx                # ❌ quay.io/library/nginx 으로 바뀌지 않음
docker pull quay.io/bitnami/nginx        # ✅ 네임스페이스를 반드시 명시해야 함

 

그래서 docker.io 이외의 레지스트리에서 이미지를 가져올 때는 반드시 네임스페이스를 포함한 풀 패스(FQIN, Fully-Qualified Image Name)를 써야 한다.

🔧 기본 레지스트리를 바꿀 수 있는가?

결론부터 말씀드리면 다음과 같다.

런타임 기본 레지스트리 변경 비고

런타임 기본 레지스트리 변경 비고
Docker Engine (순정) ❌ 불가능 docker.io로 하드코딩
Docker Engine + registry-mirrors 🔶 우회만 가능 docker.io에 대한 풀 스루 미러
Podman / CRI-O / Buildah ✅ 가능 unqualified-search-registries
RHEL 계열 패치 Docker ✅ 가능 ADD_REGISTRY, BLOCK_REGISTRY 옵션 (레거시)

1) 순정 Docker Engine — 기본 레지스트리는 바꿀 수 없다

업스트림 Docker는 기본 레지스트리를 다른 곳으로 바꾸는 옵션 자체를 제공하지 않는다. Docker Hub가 항상 최후의 검색처가 된다. 이는 의도된 설계로, 이름 충돌과 공격 표면을 줄이기 위한 보수적 선택이다.

2) registry-mirrors — 바꾸는 게 아니라 "앞에 끼워 넣는" 방식

대신 Docker는 Docker Hub에 대한 풀 스루 캐시 미러(pull-through mirror)를 등록할 수 있다. /etc/docker/daemon.json을 다음과 같이 수정하면 된다.

{
  "registry-mirrors": [
    "https://mirror.gcr.io",
    "https://registry.mycompany.internal"
  ]
}

 

그리고 데몬을 재시작한다.

sudo systemctl restart docker

 

이 설정의 동작 원리는 이렇다.

  1. docker pull nginx 요청이 들어오면
  2. Docker 데몬은 먼저 등록된 미러들을 순서대로 확인하고
  3. 미러에 이미지가 있으면 그곳에서 받고
  4. 없으면 최종적으로 docker.io로 간다

즉, 기본값이 "대체"되는 게 아니라 "앞에 끼워 넣어지는" 것이다. Docker Hub가 최종 백엔드라는 사실은 변하지 않는다. 이것이 한국·중국처럼 Docker Hub 접속이 느리거나 불안정한 지역에서 많이 쓰이는 패턴이다.

3) Podman — 진짜로 기본 레지스트리를 바꿀 수 있다

반면 Podman·Buildah·CRI-O 계열은 /etc/containers/registries.conf로 완전히 자유롭게 구성할 수 있다.

# /etc/containers/registries.conf

# 짧은 이름(예: nginx)을 찾을 레지스트리 목록 — 순서대로 탐색
unqualified-search-registries = [
  "registry.mycompany.internal",
  "quay.io",
  "docker.io"
]

# short-name 해석 모드
# "enforcing" 이면 애매할 때 사용자에게 선택을 강제한다
short-name-mode = "enforcing"

# docker.io 에 미러 붙이기
[[registry]]
prefix = "docker.io"
location = "docker.io"

  [[registry.mirror]]
  location = "mirror.mycompany.internal:5000"

 

이렇게 설정한 뒤 podman pull nginx를 실행하면, registry.mycompany.internal부터 차례로 뒤져본다. Docker보다 훨씬 유연한 것이다. 이 차이가 RHEL·OpenShift 생태계가 Docker 대신 Podman을 기본 채택한 여러 이유 중 하나이기도 하다.

⚠️ 짧은 이름이 부르는 보안 위협 — Typosquatting

기본 레지스트리를 바꿀 수 있다는 건 편리하지만, 반대로 함정이기도 하다. unqualified-search-registries에 여러 레지스트리를 등록해두면, 어느 레지스트리에서 이미지가 왔는지 애매해진다.

예를 들어 탐색 순서가 ["registry1.com", "registry2.com"]일 때, 공격자가 registry1에 nginx라는 이름의 악성 이미지를 먼저 올려두면, 진짜 nginx가 registry2에 있더라도 registry1의 악성 이미지가 먼저 받아진다. 이것이 짧은 이름 스쿼팅(short-name squatting) 공격이다.

방어 원칙 3가지

  1. FQIN을 쓰자: 프로덕션에서는 항상 docker.io/library/nginx:1.27.3 처럼 레지스트리·네임스페이스·태그를 모두 명시
  2. Digest 고정: 더 엄격하게는 nginx@sha256:72297... 형태의 해시 고정. 동일 이미지 보장
  3. Short-name-mode = "enforcing" (Podman 한정): 애매한 짧은 이름은 사용자에게 확인받도록 설정

✅ 정리

docker pull nginx라는 평범한 한 줄은 사실, 레지스트리 → 네임스페이스 → 태그 세 번의 자동 보정을 거쳐 docker.io/library/nginx:latest로 확장되는 결과물이다. 이 규칙은 Docker Hub라는 특정 레지스트리의 관용이지, 컨테이너 세계 공통의 문법이 아니다.

 

핵심을 한 줄로 요약하면 다음과 같다.

  • 순정 Docker Engine에서는 기본 레지스트리를 바꿀 수 없다 — registry-mirrors로 앞에 캐시만 끼워 넣을 수 있을 뿐이다.
  • Podman 계열에서는 unqualified-search-registries로 완전히 자유롭게 구성할 수 있다.
  • 편의성의 대가는 애매함이다 — 프로덕션에서는 반드시 FQIN 또는 digest 고정을 습관화하자.

다음 단계로 관심이 있다면, Docker Hub의 pull rate limit 회피 전략, Harbor·ECR 같은 사설 레지스트리 구축, Sigstore 기반 이미지 서명 검증(cosign), CI/CD 파이프라인의 digest pinning 자동화 순으로 학습해 나가시면 좋겠다.