---
title: "프라이빗 레지스트리 구축으로 안전하고 효율적인 Docker 이미지 관리하기"
description: "Docker Private Registry를 구축하여 내부에서 안전하게 Docker 이미지를 관리하는 방법을 설명합니다. htpasswd를 사용한 인증 설정, SSL/TLS 적용, 이미지 Push/Pull 방법, Registry API V2를 통한 이미지 조회 방법을 다루며, 트러블슈팅 및 주의사항도 포함되어 있습니다. 프로덕션 환경에서는 SSL을 필수로 적용하고, Self-Signed 인증서를 사용할 경우 클라이언트에 인증서를 등록해야 합니다."
date: "2024-09-03"
last_modified: "2026-05-20T23:53:00.000Z"
type: "Post"
tags:
  - "docker"
  - "ssl"
  - "troubleshooting"
categories:
  - "📗 Docs"
canonical_url: "https://blog.pieroot.xyz/private-registry"
markdown_url: "https://blog.pieroot.xyz/private-registry.md"
---

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

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

회사에서 프로젝트를 진행하다 보면 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 인증서는 두 가지 방법으로 준비할 수 있습니다:

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](http://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 주요 엔드포인트 정리**

#### 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일마다 갱신 필요

### 참고 자료

- [Docker Registry 공식 문서](https://docs.docker.com/registry/)

- [Docker Registry HTTP API V2](https://distribution.github.io/distribution/spec/api/)

- [Docker Registry 배포 가이드](https://distribution.github.io/distribution/about/deploying/)

- [Docker Registry 설정 레퍼런스](https://distribution.github.io/distribution/about/configuration/)
