Docker 私有仓库搭建与使用笔记
环境:registry:2 官方镜像,以
10.145.145.1:5000为例。 所有命令在服务端 (装 Docker 的 Linux 主机)或客户端(任意需要推/拉镜像的机器)执行,已标注。
目录
- 什么是私有仓库
- 搭建仓库(服务端)
- 开启认证(服务端)
- [客户端配置 insecure-registries](#客户端配置 insecure-registries "#4-%E5%AE%A2%E6%88%B7%E7%AB%AF%E9%85%8D%E7%BD%AE-insecure-registries")
- 登录仓库(客户端)
- 镜像命名规则
- 打标签
- 推送镜像
- 拉取镜像
- 查询/验证仓库内容
- 常见报错排查
- [进阶:去掉 IP 和端口](#进阶:去掉 IP 和端口 "#12-%E8%BF%9B%E9%98%B6%E5%8E%BB%E6%8E%89-ip-%E5%92%8C%E7%AB%AF%E5%8F%A3")
- 一键发布脚本
- [附:registry-mirrors 不是别名机制](#附:registry-mirrors 不是别名机制 "#14-%E9%99%84registry-mirrors-%E4%B8%8D%E6%98%AF%E5%88%AB%E5%90%8D%E6%9C%BA%E5%88%B6")
1. 什么是私有仓库
Docker Hub 是公共仓库,企业内部的镜像(业务代码、不公开的基础镜像)需要存放在自己的仓库里。官方提供 registry:2 镜像,一个容器就能搭起符合 Docker Registry API v2 协议的仓库服务。
- 公共仓库:
docker.io(写镜像名时可以省略) - 私有仓库:自建,必须写完整地址,如
10.145.145.1:5000
生产级替代品:Harbor (带 Web UI、权限管理、镜像扫描)、Nexus、Artifactory。个人/小团队用 registry:2 足够。
2. 搭建仓库(服务端)
最简版(仅本地测试,数据不持久化)
bash
docker run -d -p 5000:5000 --restart=always --name registry registry:2
推荐版(数据持久化,重启不丢镜像)
bash
mkdir -p /data/registry
docker run -d -p 5000:5000 --restart=always --name registry \
-v /data/registry/data:/var/lib/registry \
registry:2
/var/lib/registry是镜像层和 manifest 的实际存储位置,必须挂 volume。
验证服务已启动:
bash
curl http://localhost:5000/v2/
# 返回 {} 即正常
3. 开启认证(服务端)
裸仓库默认无认证,任何能连到 5000 端口的人都能推拉。用 htpasswd 加 Basic 认证:
3.1 生成密码文件
bash
mkdir -p /data/registry/auth
docker run --rm --entrypoint htpasswd httpd:2 \
-Bbn admin YourPassword123 > /data/registry/auth/htpasswd
参数说明:-B 用 bcrypt 加密(registry 只认 bcrypt)、-b 密码放命令行、-n 输出到 stdout。
追加用户时用
-Bbn 新用户 密码 >> htpasswd,注意别用>覆盖掉老用户。
3.2 带认证启动仓库
bash
docker run -d -p 5000:5000 --restart=always --name registry \
-v /data/registry/auth:/auth \
-v /data/registry/data:/var/lib/registry \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2
验证认证已生效:
bash
curl -I http://10.145.145.1:5000/v2/
# 期望返回 401 Unauthorized,响应头带 Www-Authenticate: Basic realm="Registry"
4. 客户端配置 insecure-registries
为什么需要:Docker 默认只信任 HTTPS 仓库。自建仓库没配 TLS 证书时走的是 HTTP,必须告诉 daemon"这个地址允许明文",否则推拉时报 TLS 错误。
Docker Desktop(Windows / Mac)
Settings → Docker Engine → 右侧 JSON 中加上 → Apply & Restart:
json
{
"insecure-registries": ["10.145.145.1:5000"]
}
Linux
编辑 /etc/docker/daemon.json:
json
{
"insecure-registries": ["10.145.145.1:5000"]
}
重启服务:
bash
sudo systemctl restart docker
注意:
insecure-registries只解决"能不能连上 HTTP",不含任何别名/缩写功能,镜像名里的地址一个字都省不了。
5. 登录仓库(客户端)
bash
docker login 10.145.145.1:5000
# 依次输入用户名、密码
# 出现 "Login Succeeded" 即成功
其他方式:
bash
# 非交互(密码会留在 shell 历史,不推荐)
docker login 10.145.145.1:5000 -u admin -p YourPassword123
# 非交互但安全(脚本里用这种)
echo "YourPassword123" | docker login 10.145.145.1:5000 -u admin --password-stdin
# 登出
docker logout 10.145.145.1:5000
凭据存哪了 :登录后凭据存入本机凭据管理器(~/.docker/config.json 里 "credsStore": "desktop" 表示交给系统凭据管理器保管,config.json 本身不存明文密码)。之后该机器上所有 docker push / pull 自动携带凭据,无需重复登录。
6. 镜像命名规则
完整格式:
css
[仓库地址:端口/][命名空间/]镜像名:标签
| 写法 | 实际指向 |
|---|---|
nginx |
docker.io/library/nginx:latest |
alice/myapp |
docker.io/alice/myapp:latest |
10.145.145.1:5000/app-demo:1.0 |
私有仓库,原样 |
地址判断规则 :Docker 看第一段 / 前面的内容------
- 含
.、含:、或是localhost→ 当作仓库地址 - 否则 → 当作 docker.io 上的命名空间
端口的来历 :公共仓库监听默认 443,可省略;registry:2 默认监听 5000,必须写。地址是镜像名的一部分(跟着 manifest 分发),daemon.json 里没有任何"别名"配置能把它变短,见[第 14 节](#第 14 节 "#14-%E9%99%84registry-mirrors-%E4%B8%8D%E6%98%AF%E5%88%AB%E5%90%8D%E6%9C%BA%E5%88%B6")。
7. 打标签
本地镜像必须带上仓库地址才能推送,两种做法:
bash
# 方式一:对已有镜像补标签
docker tag app-demo:1.0 10.145.145.1:5000/app-demo:1.0
# 方式二(推荐):build 时直接打全名,省一步
docker build -t 10.145.145.1:5000/app-demo:1.0 .
同一个镜像可以有多个标签,
docker tag只是加引用,不复制数据。
8. 推送镜像
bash
docker push 10.145.145.1:5000/app-demo:1.0
成功标志:最后一行返回 digest,例如:
arduino
1.0: digest: sha256:9427afca4f8e... size: 856
记下这个 digest,它是镜像内容的唯一指纹,可用于核对推送/拉取是否一致。
9. 拉取镜像
任何一台配好了 insecure-registries(第 4 节)并登录(第 5 节)的机器:
bash
docker pull 10.145.145.1:5000/app-demo:1.0
拉完核对 digest 与推送时是否一致:
bash
docker images --digests | grep app-demo
10. 查询/验证仓库内容
用 curl 调 Registry API v2(开认证的仓库要带 -u 用户:密码):
bash
# 列出所有镜像
curl -u admin:YourPassword123 http://10.145.145.1:5000/v2/_catalog
# 列出某镜像的所有标签
curl -u admin:YourPassword123 http://10.145.145.1:5000/v2/app-demo/tags/list
返回示例:
json
{"name":"app-demo","tags":["1.0"]}
⚠️
docker manifest inspect对"HTTP + Basic 认证"的仓库有兼容问题,可能误报no such manifest。验证镜像是否存在,以docker pull为准。
11. 常见报错排查
| 报错 | 原因 | 解决 |
|---|---|---|
authorization failed: no basic auth credentials |
未登录或登录过期 | docker login 仓库地址 |
http: server gave HTTP response to HTTPS client |
客户端没配 insecure-registries | 按第 4 节配置后重启 Docker |
x509: certificate signed by unknown authority |
仓库走了 HTTPS 但自签证书未被信任 | 信任证书(见下)或改走 HTTP + insecure-registries |
connection refused |
仓库容器没起 / 端口没映射 / 防火墙 | docker ps 查容器,curl http://IP:5000/v2/ 测连通 |
push 成功但 manifest inspect 报 no such manifest |
命令兼容性问题(误报) | 用 docker pull 验证 |
自签 HTTPS 证书的信任方法(Linux 客户端,每个客户端都要做):
bash
# 把仓库的 CA 证书放到下面目录(目录名 = 仓库地址:端口)
sudo mkdir -p /etc/docker/certs.d/10.145.145.1:5000
sudo cp ca.crt /etc/docker/certs.d/10.145.145.1:5000/ca.crt
# 无需重启 docker
12. 进阶:去掉 IP 和端口
镜像名里的地址会被写进部署文件(K8s yaml、compose 等),裸 IP:端口又长又不稳定。正规做法:
12.1 hosts 加短域名(改客户端)
hosts 文件(Windows:C:\Windows\System32\drivers\etc\hosts)加:
lua
10.145.145.1 reg.local
之后镜像名写 reg.local:5000/app-demo:1.0。
⚠️ 域名必须含点 :Docker 靠"第一段含
.或:"识别仓库地址, 写reg/app-demo会被当成 docker.io 上reg用户的仓库。
12.2 仓库挪到 443 端口(改服务端,去掉端口)
bash
docker run -d -p 443:5000 --restart=always --name registry ... registry:2
配合 hosts,镜像名最短变成:
bash
reg.local/app-demo:1.0
insecure-registries 里相应写 reg.local。
13. 一键发布脚本
把仓库地址收进脚本,日常命令不再出现 IP。项目根目录放 release.sh:
bash
#!/bin/bash
# 用法: ./release.sh [版本号],不传默认 1.0
REGISTRY=10.145.145.1:5000
APP=app-demo
VERSION=${1:-1.0}
docker build -t $REGISTRY/$APP:$VERSION . && \
docker push $REGISTRY/$APP:$VERSION
bash
chmod +x release.sh
./release.sh # 构建并推送 1.0
./release.sh 1.1 # 构建并推送 1.1
Windows 下等价物(PowerShell release.ps1):
powershell
param([string]$Version = "1.0")
$Registry = "10.145.145.1:5000"; $App = "app-demo"
docker build -t "$Registry/$App`:$Version" . ; if ($?) { docker push "$Registry/$App`:$Version" }
14. 附:registry-mirrors 不是别名机制
一个常见误区:以为在 daemon.json 的 registry-mirrors 里配私有仓库地址,拉镜像就能省掉 IP。
不行 。registry-mirrors 是 docker.io 的前置缓存(pull-through cache),机制是:
bash
docker pull nginx ← 只有"短名"(默认解析到 docker.io)才触发 mirror
│
▼
① 先查 mirror(路径会被规范化成 /v2/library/nginx/...),404 则
② 兜底查 docker.io
它不能当私有仓库别名用的原因:
- 只对短名生效 ------
docker pull 10.145.145.1:5000/xxx全名直奔地址,不看 mirror 配置; - pull-only ------
docker push永远不经过 mirror,推镜像照样要写全地址; - 路径不匹配 ------短名会被规范化成
library/<名字>去查询,私有仓库里不存在这个路径; - 误伤正常拉取 ------每条
docker pull nginx都要先去私有仓库吃一个 404 再兜底,多一跳延迟。
它的正确用途:给 Docker Hub 套加速缓存(国内镜像加速站就是这个原理)。
省 IP 只有两条例子正路:hosts 短域名 + 443 端口 (第 12 节),或地址收进脚本(第 13 节)。
附:命令速查
bash
# ── 服务端 ──────────────────────────────────────
docker run -d -p 5000:5000 --restart=always --name registry \
-v /data/registry/auth:/auth -v /data/registry/data:/var/lib/registry \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2 # 带认证启动
docker run --rm --entrypoint htpasswd httpd:2 -Bbn user pass > htpasswd # 生成密码
# ── 客户端 ──────────────────────────────────────
docker login 10.145.145.1:5000 # 登录
docker logout 10.145.145.1:5000 # 登出
docker tag 本地名:tag 10.145.145.1:5000/名:tag # 打标签
docker push 10.145.145.1:5000/名:tag # 推送
docker pull 10.145.145.1:5000/名:tag # 拉取
curl -u user:pass http://10.145.145.1:5000/v2/_catalog # 列镜像
curl -u user:pass http://10.145.145.1:5000/v2/名/tags/list # 列标签