Skip to document
pieroot log
Markdown Preview

프라이빗 레지스트리 구축으로 안전하고 효율적인 Docker 이미지 관리하기

Frontmatter

Docker Private Registry를 구축하여 내부에서 안전하게 Docker 이미지를 관리하는 방법을 설명합니다. htpasswd를 사용한 인증 설정, SSL/TLS 적용, 이미지 Push/Pull 방법, Registry API V2를 통한 이미지 조회 방법을 다루며, 트러블슈팅 및 주의사항도 포함되어 있습니다. 프로덕션 환경에서는 SSL을 필수로 적용하고, Self-Signed 인증서를 사용할 경우 클라이언트에 인증서를 등록해야 합니다.

프라이빗 레지스트리 구축으로 안전하고 효율적인 Docker 이미지 관리하기
회사에서 프로젝트를 진행하다 보면 Docker 이미지를 빌드하고 배포하는 일이 일상이 됩니다.
문제는 이 이미지들을 Docker Hub 같은 외부 레지스트리에 올리기 곤란한 경우가 꽤 있다는 거죠. 소스 코드가 포함된 이미지라든가, 사내 전용 설정이 들어간 이미지라든가... 보안 정책상 외부 업로드가 금지된 환경도 많습니다.
그래서 필요한 게 바로 Docker Private Registry입니다. 로컬 네트워크에 나만의 이미지 저장소를 만들어서, 외부 유출 걱정 없이 이미지를 관리할 수 있습니다.
이번 글에서는 Registry 설치부터 인증 설정, SSL 적용, 이미지 Push/Pull, 그리고 API를 통한 이미지 조회까지 한 번에 정리해보겠습니다.
📌
이 글에서 다루는 내용
  • Docker Private Registry란 무엇인가
  • htpasswd 기반 인증(Authentication) 설정
  • Registry 컨테이너 실행
  • SSL/TLS 인증서 적용으로 HTTPS 통신 구성
  • 이미지 Push/Pull 방법
  • Registry HTTP API V2로 이미지 목록 조회
  • 트러블슈팅 및 주의사항

🤔 Docker Private Registry란?

Docker Registry는 Docker 이미지를 저장하고 배포하기 위한 서버 애플리케이션입니다. Docker Hub가 대표적인 퍼블릭 레지스트리이며, Private Registry는 이를 자체 인프라에서 운영하는 것입니다.
쉽게 말해서, 내 서버에 Docker Hub를 하나 띄우는 것이라고 생각하면 됩니다. Docker에서 공식적으로 registry 이미지를 제공하고 있어서, 컨테이너 하나만 띄우면 바로 사용할 수 있습니다.
💡
Private Registry를 사용하는 이유
  • 보안: 소스 코드가 포함된 이미지를 외부에 노출하지 않음
  • 속도: 로컬 네트워크에서 Pull/Push하므로 Docker Hub보다 훨씬 빠름
  • 네트워크 제한 환경: 인터넷 접근이 제한된 폐쇄망에서도 이미지 배포 가능
  • 비용: Docker Hub 무료 플랜은 Private Repository 수에 제한이 있음

🔐 인증(Authentication) 설정

Registry를 아무나 접근할 수 있게 열어두면... 보안팀에게 혼나기 딱 좋습니다. htpasswd를 사용하여 HTTP Basic Authentication을 설정합니다.

인증 파일 생성

먼저 인증 정보를 저장할 디렉토리를 만들고, htpasswd 파일을 생성합니다.
bash
mkdir auth
httpd 이미지에 포함된 htpasswd 유틸리티를 사용하여 사용자 인증 파일을 생성합니다:
bash
docker run --entrypoint htpasswd httpd:2 -Bbn admin <CHANGE_REGISTRY_PASSWORD> > auth/htpasswd
📝
명령어 해설
  • --entrypoint htpasswd: httpd 컨테이너의 기본 엔트리포인트 대신 htpasswd 명령을 실행
  • -B: bcrypt 알고리즘으로 암호화 (가장 강력한 옵션)
  • -b: 커맨드라인에서 비밀번호를 직접 전달
  • -n: 결과를 stdout으로 출력 (파일에 직접 쓰지 않음)
  • admin: 사용자 이름
  • <CHANGE_REGISTRY_PASSWORD>: 비밀번호 (반드시 변경하세요!)
⚠️
registry:2.7.1 이후 버전 주의!
Registry 이미지 2.7.1 이후 버전에서는 컨테이너 내부에 htpasswd가 포함되지 않아 registry 이미지로 직접 인증 파일을 생성할 수 없습니다. 위처럼 httpd:2 이미지를 사용하거나, 호스트에 apache2-utils를 설치하여 사용하세요:
bash
# Ubuntu/Debian
sudo apt-get install apache2-utils
htpasswd -Bbn admin <CHANGE_REGISTRY_PASSWORD> > auth/htpasswd
사용자를 추가하고 싶다면 >>(append)를 사용합니다:
bash
docker run --entrypoint htpasswd httpd:2 -Bbn developer <CHANGE_DEVELOPER_PASSWORD> >> auth/htpasswd
생성된 파일을 확인해보면 이런 형태입니다:
bash
cat auth/htpasswd
# admin:$2y$05$...해시된 비밀번호...
# developer:$2y$05$...해시된 비밀번호...

🚀 Registry 컨테이너 실행

기본 실행 (HTTP + 인증)

인증 파일이 준비되었으면 Registry 컨테이너를 띄웁니다:
bash
docker run -d -p 5000:5000 --restart=always --name registry \
  -v "$(pwd)"/auth:/auth \
  -v registry_data:/var/lib/registry \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:latest
📝
옵션 상세 해설
  • -d: 백그라운드(데몬) 모드로 실행
  • -p 5000:5000: 호스트의 5000번 포트를 컨테이너의 5000번 포트에 매핑
  • --restart=always: Docker 데몬이 재시작되어도 자동으로 컨테이너를 시작
  • -v "$(pwd)"/auth:/auth: 호스트의 auth 디렉토리를 컨테이너에 마운트
  • -v registry_data:/var/lib/registry: 이미지 데이터를 Docker Volume에 영구 저장 (이게 없으면 컨테이너 삭제 시 모든 이미지가 사라집니다)
  • REGISTRY_AUTH_HTPASSWD_REALM: 인증 창에 표시될 이름
  • REGISTRY_AUTH_HTPASSWD_PATH: 컨테이너 내부의 htpasswd 파일 경로
실행 확인:
bash
docker ps | grep registry

# 예상 출력:
# CONTAINER ID  IMAGE            ...  STATUS        PORTS                    NAMES
# abc123def456  registry:latest  ...  Up 3 seconds  0.0.0.0:5000->5000/tcp   registry

로그인 테스트

bash
docker login localhost:5000
# Username: admin
# Password: <CHANGE_REGISTRY_PASSWORD>
# Login Succeeded
Login Succeeded가 나오면 인증 설정이 정상적으로 된 것입니다. 🎉

🔒 SSL/TLS 적용 (HTTPS)

HTTP로만 운영하면 네트워크 상에서 인증 정보가 평문으로 노출됩니다. 해커의 밥상 차려주는 격이죠. 프로덕션 환경에서는 반드시 SSL/TLS를 적용해야 합니다.

인증서 준비

SSL 인증서는 두 가지 방법으로 준비할 수 있습니다:
방법
장점
단점
적합한 환경
Let's Encrypt
무료, 브라우저 신뢰
도메인 필요, 갱신 필요
외부 접근 가능한 서버
Self-Signed
즉시 생성 가능, 도메인 불필요
클라이언트에 인증서 등록 필요
내부망, 폐쇄망

Self-Signed 인증서 생성

내부 환경에서는 자체 서명 인증서를 사용하는 경우가 많습니다:
bash
# 인증서 저장 디렉토리 생성
mkdir -p auth/ssl

# 자체 서명 인증서 생성 (유효기간 365일)
openssl req -newkey rsa:4096 -nodes -sha256 \
  -keyout auth/ssl/server.key \
  -x509 -days 365 \
  -out auth/ssl/server.crt \
  -subj "/C=KR/ST=Seoul/L=Seoul/O=MyCompany/CN=registry.example.com" \
  -addext "subjectAltName=DNS:registry.example.com,IP:172.30.0.11"
💡
인증서 생성 옵션 설명
  • -newkey rsa:4096: 4096비트 RSA 키 생성
  • -nodes: 개인키를 암호화하지 않음 (Registry가 자동으로 읽을 수 있도록)
  • -sha256: SHA-256 해시 알고리즘 사용
  • -days 365: 인증서 유효기간 365일
  • -subj: 인증서 주체 정보 (환경에 맞게 수정)
  • -addext "subjectAltName=...": SAN(Subject Alternative Name) 추가. Docker 클라이언트가 이 필드를 확인하므로 반드시 설정해야 합니다

SSL 적용하여 Registry 실행

기존 Registry를 중지하고, SSL이 적용된 새 컨테이너를 실행합니다:
bash
# 기존 컨테이너 중지 및 삭제
docker stop registry
docker rm registry
bash
# SSL 적용된 Registry 실행
docker run -d -p 5000:5000 --restart=always --name registry \
  -v "$(pwd)"/auth:/auth \
  -v registry_data:/var/lib/registry \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/ssl/server.crt \
  -e REGISTRY_HTTP_TLS_KEY=/auth/ssl/server.key \
  registry:latest
기존 실행 명령어에 TLS 관련 환경변수 두 줄만 추가된 것입니다:
bash
-e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/ssl/server.crt \
-e REGISTRY_HTTP_TLS_KEY=/auth/ssl/server.key
⚠️
중요! 인증서 파일(server.crt, server.key)은 반드시 컨테이너 내부 경로를 기준으로 작성해야 합니다. 위 예시에서는 호스트의 auth/ssl/ 디렉토리가 컨테이너의 /auth/ssl/로 마운트되므로 /auth/ssl/server.crt로 지정합니다.

클라이언트에 인증서 등록 (Self-Signed 사용 시)

자체 서명 인증서를 사용하면 Docker 클라이언트가 인증서를 신뢰하지 않아 Push/Pull이 실패합니다. 클라이언트 머신에 인증서를 등록해야 합니다:
bash
# 인증서 복사 (registry.example.com을 실제 주소로 변경)
sudo mkdir -p /etc/docker/certs.d/registry.example.com:5000
sudo cp server.crt /etc/docker/certs.d/registry.example.com:5000/ca.crt

# Docker 데몬 재시작
sudo systemctl restart docker
📝
디렉토리 구조
/etc/docker/certs.d/ 하위에 <레지스트리주소>:<포트>/ 디렉토리를 만들고, 그 안에 ca.crt 파일을 넣으면 됩니다. Docker 데몬이 이 디렉토리를 자동으로 탐색합니다.
javascript
/etc/docker/certs.d/
└── registry.example.com:5000/
    └── ca.crt
또는 시스템 전역으로 인증서를 신뢰하도록 등록할 수도 있습니다:
bash
# Ubuntu/Debian
sudo cp server.crt /usr/local/share/ca-certificates/registry.crt
sudo update-ca-certificates

# CentOS/RHEL
sudo cp server.crt /etc/pki/ca-trust/source/anchors/registry.crt
sudo update-ca-trust

📦 이미지 Push / Pull

Registry가 준비되었으니 실제로 이미지를 올리고 내려받아 봅시다.

이미지 Push

bash
# 1. 로그인
docker login registry.example.com:5000

# 2. 기존 이미지에 Registry 태그 추가
docker tag my-app:latest registry.example.com:5000/my-app:latest

# 3. Push
docker push registry.example.com:5000/my-app:latest
💡
태그 규칙
Private Registry에 Push하려면 이미지 이름 앞에 Registry 주소를 붙여야 합니다:
<registry-host>:<port>/<image-name>:<tag>
Docker Hub에 Push할 때 <username>/<image>를 쓰는 것과 같은 원리입니다.

이미지 Pull

다른 머신에서 이미지를 내려받으려면:
bash
# 1. 로그인
docker login registry.example.com:5000

# 2. Pull
docker pull registry.example.com:5000/my-app:latest

localhost 사용 시 (같은 서버에서)

Registry가 돌아가는 같은 서버에서는 localhost로 접근할 수 있습니다:
bash
docker tag my-app:latest localhost:5000/my-app:latest
docker push localhost:5000/my-app:latest
📝
localhost로 접근하는 경우에는 Docker가 SSL 검증을 건너뛰기 때문에 Self-Signed 인증서 등록 없이도 바로 사용할 수 있습니다. 하지만 이건 같은 서버에서만 동작하는 예외이므로, 다른 머신에서 접근할 때는 반드시 인증서를 등록해야 합니다.

🔍 Registry API로 이미지 조회

Docker Private Registry는 HTTP API V2를 제공합니다. 이 API를 통해 저장된 이미지 목록이나 태그 정보를 조회할 수 있습니다.

저장된 이미지(Repository) 목록 조회

bash
curl -u admin:<CHANGE_REGISTRY_PASSWORD> -k \
  https://registry.example.com:5000/v2/_catalog
응답 예시:
json
{
  "repositories": [
    "my-app",
    "nginx-custom",
    "python-ml"
  ]
}

특정 이미지의 태그 목록 조회

bash
curl -u admin:<CHANGE_REGISTRY_PASSWORD> -k \
  https://registry.example.com:5000/v2/my-app/tags/list
응답 예시:
json
{
  "name": "my-app",
  "tags": [
    "latest",
    "v1.0.0",
    "v1.1.0"
  ]
}

이미지 Manifest 조회

특정 태그의 상세 정보(레이어, 다이제스트 등)를 확인하려면:
bash
curl -u admin:<CHANGE_REGISTRY_PASSWORD> -k \
  -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
  https://registry.example.com:5000/v2/my-app/manifests/latest
💡
API 주요 엔드포인트 정리
엔드포인트
설명
GET /v2/_catalog
전체 Repository 목록
GET /v2/<name>/tags/list
특정 이미지의 태그 목록
GET /v2/<name>/manifests/<ref>
이미지 Manifest 조회
DELETE /v2/<name>/manifests/<digest>
이미지 삭제
GET /v2/
API 버전 확인 (Health Check 용도)

Health Check

Registry가 정상 동작하는지 간단히 확인하려면:
bash
curl -k https://registry.example.com:5000/v2/
# 정상 응답: {}

🐳 Docker Compose로 관리하기 (선택사항)

매번 긴 docker run 명령어를 치는 건 솔직히 고문입니다. Docker Compose를 사용하면 설정을 파일로 관리할 수 있어서 훨씬 편합니다.
yaml
# docker-compose.yml
version: '3.8'

services:
  registry:
    image: registry:latest
    container_name: registry
    restart: always
    ports:
      - "5000:5000"
    volumes:
      - ./auth:/auth
      - registry_data:/var/lib/registry
    environment:
      REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_HTTP_TLS_CERTIFICATE: /auth/ssl/server.crt
      REGISTRY_HTTP_TLS_KEY: /auth/ssl/server.key

volumes:
  registry_data:
bash
# 실행
docker compose up -d

# 중지
docker compose down

# 로그 확인
docker compose logs -f registry

🔧 트러블슈팅

x509: certificate signed by unknown authority

Self-Signed 인증서를 사용할 때 가장 흔한 에러입니다.
bash
# 해결: 클라이언트에 인증서 등록
sudo mkdir -p /etc/docker/certs.d/<registry-host>:5000
sudo cp server.crt /etc/docker/certs.d/<registry-host>:5000/ca.crt
sudo systemctl restart docker

http: server gave HTTP response to HTTPS client

Docker 클라이언트가 HTTPS로 접속하려는데 Registry가 HTTP로만 동작하는 경우입니다.
SSL을 설정하지 않은 상태에서 사용하려면 Docker 데몬에 insecure registry를 등록해야 합니다:
bash
# /etc/docker/daemon.json
{
  "insecure-registries": ["registry.example.com:5000"]
}
bash
sudo systemctl restart docker
⚠️
insecure-registries는 테스트 환경에서만 사용하세요! 이 설정은 SSL 검증을 완전히 비활성화하므로, 프로덕션 환경에서는 반드시 SSL을 적용하는 것을 권장합니다.

Push/Pull이 느린 경우

Docker는 기본적으로 이미지 레이어를 동시에 5개까지 업로드합니다. 대역폭이 제한된 환경에서는 이를 조절할 수 있습니다:
bash
# /etc/docker/daemon.json
{
  "max-concurrent-uploads": 2,
  "max-concurrent-downloads": 3
}

이미지 삭제가 안 되는 경우

Registry의 기본 설정에서는 이미지 삭제 API가 비활성화되어 있습니다. 삭제를 허용하려면 환경변수를 추가합니다:
bash
-e REGISTRY_STORAGE_DELETE_ENABLED=true
삭제 후에는 가비지 컬렉션을 실행하여 실제 디스크 공간을 회수해야 합니다:
bash
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml

핵심 정리

✅ Docker Private Registry: 공식 registry 이미지로 컨테이너 하나만 띄우면 내부 이미지 저장소 완성
✅ htpasswd 인증: httpd:2 이미지의 htpasswd 유틸리티로 사용자 인증 파일 생성
✅ SSL/TLS 필수: 프로덕션 환경에서는 반드시 SSL을 적용하여 인증 정보 보호
✅ Self-Signed 인증서: 내부망에서는 자체 서명 인증서 사용 가능, 클라이언트에 ca.crt 등록 필요
✅ Registry API V2: /v2/_catalog로 이미지 목록, /v2/<name>/tags/list로 태그 조회
✅ Docker Compose: 설정을 YAML로 관리하면 유지보수가 훨씬 편리

주의사항

⚠️ 볼륨 마운트 필수: -v registry_data:/var/lib/registry 없이 실행하면 컨테이너 삭제 시 모든 이미지 데이터 소실
⚠️ insecure-registries 지양: 테스트 환경에서만 사용, 프로덕션에서는 SSL 적용
⚠️ 인증서 SAN 설정: Self-Signed 인증서 생성 시 subjectAltName을 반드시 포함
⚠️ 비밀번호 관리: htpasswd 파일에 기록된 비밀번호는 bcrypt로 해시되지만, 파일 자체의 접근 권한을 제한하세요
⚠️ 인증서 갱신: Self-Signed는 유효기간, Let's Encrypt는 90일마다 갱신 필요

참고 자료