プライベートDockerレジストリを構築する方法|registryコンテナの運用とDocker Hub rate limit対策

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > プライベートDockerレジストリを構築する方法|registryコンテナの運用とDocker Hub rate limit対策
「Docker Hubのpull上限(rate limit)に引っかかって、CIパイプラインが止まった」「社内で作ったカスタムイメージをインターネット上に置きたくない」——
こういった問題に直面したことはないだろうか。

Docker Hubは無料プランだと6時間ごとに100回のpull制限がある。開発チームが増えてくると、CIが同時走行するたびにtoomanyrequestsエラーが返ってきてビルドが止まる。

この記事では、Dockerが公式に提供するregistryコンテナを使って、社内LAN上にプライベートDockerレジストリを構築する方法を解説する。docker composeによる基本構成から、TLS証明書・htpasswd認証によるセキュア化、さらにDocker HubのPull through Cacheとして使う設定まで、RHEL 9.4 / Ubuntu 24.04 LTSで動作確認した実行例付きで紹介する。

この記事のポイント

・docker run registry:2 で5分以内にプライベートレジストリを起動できる
・push/pullはイメージに「ホスト:ポート/」タグを付けるだけでよい
・TLS + htpasswd認証でセキュアな社内専用レジストリを構築できる
・pull through cache設定でDocker Hub rate limitを実質回避できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

プライベートDockerレジストリとは(Docker Hubとの違い・用途)

Dockerのイメージ配信は、デフォルトでDocker Hub(hub.docker.com)に接続する。便利ではあるが、実務でいくつかの問題が顕在化する。

Docker Hubの主な制約:
・無料プランは6時間ごとにpull数が100回に制限される(認証済みユーザーは200回)
・プライベートリポジトリ数に上限がある(無料プランは1リポジトリのみ)
・インターネット経由のため、pull速度がネットワーク環境に依存する
・社内のカスタムイメージを外部サービスに置くことによるセキュリティリスク

プライベートDockerレジストリを社内に構築すれば、これらの問題をまとめて解決できる。

プライベートレジストリの主なユースケース:
・CI/CDパイプラインでのベースイメージをキャッシュして、rate limitとビルド時間を削減する
・社内ネットワーク内でカスタムイメージを安全に共有する
・Docker Hub rate limitが原因のビルド失敗を根絶する

Dockerが公式に提供するレジストリイメージはregistry:2(Distribution)と呼ばれる。Go製で軽量・高速であり、本番利用にも耐える。

registryコンテナを立ち上げる(docker composeによる基本構成)

まずはHTTPSなしのシンプルな構成で動かしてみる。動作を理解してから認証・TLSを追加するのが、遠回りに見えて結局は近道だ。

1. ディレクトリ構成を準備する

作業ディレクトリを作成する。

# create working directories for registry mkdir -p ~/registry/{data,certs,auth} cd ~/registry

data/はイメージデータの保存先、certs/はTLS証明書、auth/はhtpasswdファイルを置く場所だ。

2. docker-compose.yml を作成する

以下の内容でdocker-compose.ymlを作成する。

services: registry: image: registry:2 container_name: registry restart: always ports: - "5000:5000" volumes: - ./data:/var/lib/registry environment: REGISTRY_STORAGE_DELETE_ENABLED: "true"

REGISTRY_STORAGE_DELETE_ENABLED: "true"を設定しておくことで、後からイメージの削除(garbage collect)が行えるようになる。

3. コンテナを起動して動作確認する

# start the registry container docker compose up -d # confirm it is running docker compose ps

curlでAPIに応答があれば起動成功だ。

$ curl -s http://localhost:5000/v2/ {}

{}が返れば正常に動作している。ポートの開放状態を確認したい場合は、Linux ポート確認の全コマンドを参照してほしい。

イメージのpush/pullを試す(タグ付け・ローカル転送の実例)

registryが起動したら、実際にイメージをpush・pullしてみる。

1. ローカルイメージにタグを付ける

プライベートレジストリへpushするには、イメージ名に「ホスト名:ポート番号/」のプレフィックスを付けたタグが必要だ。

# pull from Docker Hub (needed only on first time) docker pull nginx:latest # tag the image for our private registry docker tag nginx:latest localhost:5000/nginx:latest

2. プライベートレジストリへpushする

$ docker push localhost:5000/nginx:latest The push refers to repository [localhost:5000/nginx] 9e3f92c03290: Pushed d0e26daf1f58: Pushed 2c7e9b31bd18: Pushed d2a37b3d0ba5: Pushed fccbe3c16ded: Pushed latest: digest: sha256:a4b2c3d4e5f6789012abcdef1234567890abcdef1234567890abcdef12345678 size: 1862

各レイヤーが順番にpushされ、最後にdigestが表示されれば成功だ。

3. プライベートレジストリからpullする

ローカルのキャッシュを削除してから、プライベートレジストリからpullしてみる。

# remove local cache to verify pull from private registry docker rmi localhost:5000/nginx:latest $ docker pull localhost:5000/nginx:latest latest: Pulling from nginx d2a37b3d0ba5: Pull complete 2c7e9b31bd18: Pull complete fccbe3c16ded: Pull complete 9e3f92c03290: Pull complete Digest: sha256:a4b2c3d4e5f6789012abcdef1234567890abcdef1234567890abcdef12345678 Status: Downloaded newer image for localhost:5000/nginx:latest localhost:5000/nginx:latest

Docker Hubではなく社内レジストリからpullされていることがわかる。

HTTPS/認証を付加してセキュアに運用する(TLS証明書・htpasswd)

ここまでの構成はHTTPで動いているため、社内LAN内での評価目的なら問題ないが、本番運用ではTLS暗号化と認証が必須だ。DockerクライアントはデフォルトでHTTPSを要求する(localhost以外のホストに対して)。

1. TLS証明書を準備する

検証サーバーで自己署名証明書を作成する例を示す。本番ではLet's Encryptや社内CAの証明書を使うことを推奨する。なお、TLS証明書の有効性は時刻同期が前提となるため、事前にntpd 時刻同期設定を確認しておくこと。

# generate a self-signed certificate valid for 365 days $ openssl req -newkey rsa:4096 -nodes -sha256 -keyout ~/registry/certs/domain.key -x509 -days 365 -out ~/registry/certs/domain.crt -subj "/CN=registry.local" Generating a RSA private key ...............++++ writing RSA key # 192.168.x.x registry.local (実際のIPに置き換えること)

DNSでregistry.localを解決できるようにする場合は、Linux DNS 設定の基本を参考にしてほしい。

Dockerクライアント側では、自己署名証明書を信頼済みCAとして登録する必要がある。

# register the self-signed cert as trusted CA (RHEL/Rocky Linux) sudo mkdir -p /etc/docker/certs.d/registry.local:5000 sudo cp ~/registry/certs/domain.crt /etc/docker/certs.d/registry.local:5000/ca.crt # Ubuntu: update system trust store sudo cp ~/registry/certs/domain.crt /usr/local/share/ca-certificates/registry-local.crt sudo update-ca-certificates

2. htpasswdで認証ファイルを作成する

httpd-tools(RHEL系)またはapache2-utils(Ubuntu系)パッケージに含まれるhtpasswdコマンドで認証ファイルを作成する。

# install htpasswd tool (RHEL/Rocky Linux) sudo dnf install -y httpd-tools # install htpasswd tool (Ubuntu) sudo apt-get install -y apache2-utils # create password file with bcrypt hashing (-B is recommended) $ htpasswd -Bc ~/registry/auth/htpasswd admin New password: Re-type new password: Adding password for user admin

3. docker-compose.ymlを更新する

TLSと認証を有効にしたdocker-compose.ymlに更新する。

services: registry: image: registry:2 container_name: registry restart: always ports: - "5000:5000" volumes: - ./data:/var/lib/registry - ./certs:/certs - ./auth:/auth environment: REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt REGISTRY_HTTP_TLS_KEY: /certs/domain.key REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm" REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd REGISTRY_STORAGE_DELETE_ENABLED: "true"

4. docker loginで認証テストをする

# restart the registry to apply TLS settings docker compose down && docker compose up -d # test login to the secure registry $ docker login registry.local:5000 Username: admin Password: WARNING! Your password will be stored unencrypted in /home/user/.docker/config.json. Login Succeeded

ログイン成功後は、docker pull registry.local:5000/nginx:latestのように認証済みでpullできるようになる。

Docker Hub rate limitの実態と対策(pull through cacheとして使う方法)

Docker Hubのrate limitは、開発チームが増えると深刻な問題になる。CIパイプラインで複数のジョブが並走する環境では、認証なしだと6時間ごとにわずか100pullしか使えない。

1. rate limitの現状を確認する

現在の残りpull数は以下のコマンドで確認できる。

# check remaining pull quota from Docker Hub TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])") curl -s --head -H "Authorization: Bearer ${TOKEN}" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit

実際の検証サーバーでの出力例(IPアドレス末尾はマスク済み):

ratelimit-limit: 100;w=21600 ratelimit-remaining: 47;w=21600 docker-ratelimit-source: 203.0.113.xx

ratelimit-remaining: 47は残り47回であることを示す。このまま放置するとCIパイプライン停止の原因になる。

2. pull through cacheの設定

registryをDocker Hubのキャッシュとして動作させる。チームで一度pullしてしまえば、以降は社内レジストリから取得できる。

services: registry-cache: image: registry:2 container_name: registry-cache restart: always ports: - "5001:5000" volumes: - ./cache-data:/var/lib/registry environment: REGISTRY_PROXY_REMOTEURL: "https://registry-1.docker.io" # optional: add docker hub credentials to increase rate limit # REGISTRY_PROXY_USERNAME: "your-dockerhub-username" # REGISTRY_PROXY_PASSWORD: "your-dockerhub-token"

Dockerデーモン側でこのキャッシュレジストリを使うよう設定する。/etc/docker/daemon.jsonを以下の内容で作成または編集する(既存の設定を上書きしないよう注意すること)。

{ "registry-mirrors": ["http://registry.local:5001"] }

設定後にDockerデーモンを再起動し、ミラー設定が反映されていることを確認する。

sudo systemctl restart docker # verify the mirror is registered $ docker info | grep -A 5 "Registry Mirrors" Registry Mirrors: http://registry.local:5001/

以降、docker pull nginx:latestのように通常通りpullするだけで、2回目以降はDocker Hubには接続せず社内キャッシュから取得される。

実運用Tips(容量管理・garbage collect・バックアップ)

1. ストレージ使用量を確認する

data/ディレクトリの使用量はduコマンドで確認できる。

# check storage usage $ du -sh ~/registry/data/ 4.8G /home/admin/registry/data/ # list all repositories via API $ curl -s https://registry.local:5000/v2/_catalog --cacert ~/registry/certs/domain.crt -u admin:yourpassword {"repositories":["nginx","ubuntu","myapp"]}

イメージのタグ一覧は/v2/リポジトリ名/tags/listエンドポイントで確認できる。

2. garbage collectで不要レイヤーを削除する

docker rmiでイメージを削除しても、ディスクのレイヤーファイルはすぐには消えない。garbage-collectコマンドを実行して物理削除する。

# dry-run: show what would be deleted without actually deleting $ docker exec registry bin/registry garbage-collect --dry-run /etc/docker/registry/config.yml myapp: marking manifest sha256:1a2b3c4d... nginx: marking manifest sha256:a4b2c3d4... 3 blobs marked, 15 blobs and 5 manifests eligible for deletion blob eligible for deletion: sha256:deadbeef... # stop the registry, run garbage-collect, then restart docker compose stop docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml docker compose start

注意: garbage collectはレジストリ稼働中でも実行できるが、push中のイメージが破損するリスクがある。メンテナンス時間帯にレジストリを停止してから実行するのがベストプラクティスだ。

3. レジストリデータをバックアップする

レジストリのデータはすべてdata/ディレクトリ以下に集約されている。このディレクトリをそのままアーカイブするだけでバックアップが完了する。マウントの管理についてはmount コマンドの使い方も参照してほしい。

# stop the registry before backup to ensure data consistency docker compose stop tar czf /backup/registry-data-$(date +%Y%m%d).tar.gz -C ~/registry data/ docker compose start # verify the backup file was created $ ls -lh /backup/registry-data-*.tar.gz -rw-r--r-- 1 admin admin 4.2G Jul 17 03:00 /backup/registry-data-20260717.tar.gz

定期バックアップはcronに登録しておくと安心だ。

よくあるエラーと対処法

1. 「http: server gave HTTP response to HTTPS client」エラー

HTTPのレジストリにDockerクライアントがHTTPSで接続しようとした場合に発生する。

Error response from daemon: Get "https://registry.local:5000/v2/": http: server gave HTTP response to HTTPS client

対処法は2つある。

方法A: TLSを設定する(推奨)
本記事の「HTTPS/認証を付加してセキュアに運用する」セクションを参照してTLSを設定する。

方法B: insecure-registriesに追加する(開発・検証環境のみ)

# /etc/docker/daemon.json に insecure-registries を追加する { "insecure-registries": ["registry.local:5000"] }

本番環境でのinsecure-registriesの使用は推奨しない。

2. 「x509: certificate signed by unknown authority」エラー

自己署名証明書がDockerクライアントに信頼されていない場合に発生する。

Error response from daemon: Get "https://registry.local:5000/v2/": x509: certificate signed by unknown authority

/etc/docker/certs.d/registry.local:5000/ca.crtに証明書が正しくコピーされているか確認すること。また、TLS証明書の時刻有効性は時刻同期が正しく設定されているかどうかに依存するため、ntpd 時刻同期設定も確認してほしい。

3. 「no basic auth credentials」エラー

認証を設定したレジストリに未ログインの状態でアクセスしようとした場合に発生する。

Error response from daemon: Head "https://registry.local:5000/v2/nginx/manifests/latest": no basic auth credentials

docker login registry.local:5000を実行してから再試行すること。

まとめ

プライベートDockerレジストリの構築・運用に必要な手順を一覧にする。

やりたいこと コマンド
レジストリを起動する docker compose up -d
API動作確認 curl -s http://localhost:5000/v2/
イメージにタグを付ける docker tag nginx:latest localhost:5000/nginx:latest
プライベートレジストリへpushする docker push localhost:5000/nginx:latest
プライベートレジストリからpullする docker pull localhost:5000/nginx:latest
レジストリへログインする docker login registry.local:5000
garbage collectを実行する docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
リポジトリ一覧を取得する curl -s https://registry.local:5000/v2/_catalog

プライベートDockerレジストリは、インフラが小規模なうちから導入しておくことを強くすすめる。Docker Hub rate limitはチームが大きくなるほど影響が大きくなる。pull through cache設定を加えれば、既存のDockerワークフローを変えずにrate limit問題を根絶できる。

次に読む記事:
Linux ポート確認の全コマンド(registryが使うポート5000の開放確認に)
Linux DNS 設定の基本(registry.localのDNS解決設定に)
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Linux無料マニュアルを受け取る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。