Docker Registry 是 Docker 官方提供的私有镜像仓库实现,一个容器就能跑起来,适合在内网存放自己构建的镜像,省去把镜像推到 Docker Hub 再拉下来的折腾。本文整理不启用认证的开放式部署:registry 服务 + Web 浏览前端,以及客户端如何配置、推送、拉取和清理镜像。
一、先明确风险:无认证意味着什么
这篇的部署方式是「明文 HTTP + 不设账号密码」,也就是任何能访问到 5000 端口的人都可以:
- 拉取仓库里的全部镜像;
- 推送同名镜像覆盖你的 tag;
- 如果开启了删除功能,还能直接删掉镜像。
所以这套方案只适合内网、VPN 内或完全受控的网络环境,不要把端口直接暴露到公网。如果确实需要对外提供,请改用 HTTPS + htpasswd,见文末附录。客户端侧要求配置 insecure-registries,本质上就是在告诉 Docker「我知道这个仓库走的是不加密的连接,放行吧」。
二、用 docker-compose 部署 Registry
1. 准备目录
mkdir -p registry/data && cd registry2. docker-compose.yml
version: "3.8"
services:
registry:
privileged: true # 见下方说明,非必需
image: registry:2 # 镜像来源
restart: always # 开机后自动启动
container_name: registry # 自定义容器名
ports: # 暴露端口
- '5000:5000'
volumes: # 挂载目录配置
- ./data:/var/lib/registry
environment:
# - REGISTRY_STORAGE_DELETE_ENABLED="true" # 需要删除镜像功能时开启此项
- REGISTRY_HTTP_ADDR=0.0.0.0:5000
- REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry
- REGISTRY_HTTP_TLS_CERTIFICATE="" # 显式置空 = 不启用 TLS
- REGISTRY_HTTP_TLS_KEY=""
web:
image: konradkleine/docker-registry-frontend:v2
container_name: registry_web
restart: always
ports:
- '8035:80'
environment:
- ENV_DOCKER_REGISTRY_HOST=repo.ygzinfo.cn # 写对外访问的域名或公网 IP
- ENV_DOCKER_REGISTRY_PORT= # 对外开放端口非 80/443 时不能留空,要写具体端口号3. 关键配置项说明
| 配置项 | 取值 | 说明 |
|---|---|---|
image | registry:2 | Docker 官方 Registry v2 镜像,容器内默认监听 5000 端口 |
ports | 5000:5000 | 把容器 5000 映射到宿主机 5000,之后镜像地址就是 IP:5000 |
volumes | ./data:/var/lib/registry | 镜像层与元数据全部落在这个目录,务必挂出来,否则容器重建后镜像全丢 |
REGISTRY_HTTP_ADDR | 0.0.0.0:5000 | 监听所有网卡。若写成 127.0.0.1,容器外部将无法访问 |
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY | /var/lib/registry | 存储驱动为 filesystem 时的根目录,与上面的挂载点对应 |
REGISTRY_STORAGE_DELETE_ENABLED | true(默认注释掉) | 开启后才允许通过 API 删除 manifest,不开则删除请求会被拒绝 |
REGISTRY_HTTP_TLS_CERTIFICATE / _KEY | 空字符串 | 显式置空表示不启用 TLS。本方案走明文 HTTP;不写这两项效果相同,可以删掉 |
关于 privileged: true:官方 registry:2 镜像本身以 root 运行、直接写挂载目录,正常运行并不需要特权模式。这里保留它是为了排除挂载目录权限带来的干扰;如果 ./data 目录属主、权限都正常,可以去掉这一行。反过来,如果容器起不来、报写入失败,优先检查 ./data 的权限而不是加特权。
4. 启动服务
sudo docker-compose up -d
# 查看日志
sudo docker-compose logs -f registry
# 验证 API 是否正常,返回 200 和一对空花括号即表示服务就绪
curl -i http://10.1.223.2:5000/v2/三、客户端配置:insecure-registries
因为服务端走的是明文 HTTP,每一台要推拉镜像的机器都要在 Docker 守护进程配置里声明这是可信任的非安全仓库,否则会报 http: server gave HTTP response to HTTPS client。
编辑 /etc/docker/daemon.json:
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"http://10.1.223.2:5000"
],
"data-root": "/www/docker",
"insecure-registries": ["10.1.223.2:5000"]
}改完重启 Docker 生效:
sudo systemctl daemon-reload
sudo systemctl restart docker两点容易搞混的地方:
registry-mirrors只对 Docker Hub 生效,是给官方镜像做加速的,不会代理你自己搭的 registry。它和insecure-registries是两回事,自建仓库能不能明文访问只取决于后者。insecure-registries里写的是主机:端口,端口必须带上;写成域名时同样要带端口。
四、推送和拉取镜像
镜像引用必须是 仓库地址/仓库名:标签 的格式,以 registry 地址开头 Docker 才知道要推到哪里去,否则默认会推到 Docker Hub。
# 1. 标记本地镜像,加上 registry 地址
docker tag nginx:latest 10.1.223.2:5000/library/nginx:latest
# 2. 推送到 Registry
docker push 10.1.223.2:5000/library/nginx:latest
# 3. 从 Registry 拉取镜像
docker pull 10.1.223.2:5000/library/nginx:latest本地自己构建的镜像也一样,先 docker build,再 docker tag 成带仓库地址的形式,然后 push。仓库名里的 library/ 只是沿用 Docker Hub 的命名习惯,Registry v2 并不强制要求命名空间。
由于没有开启认证,不需要执行 docker login,直接 push/pull 即可。想查看仓库里都有哪些镜像,可以调用目录接口:
curl http://10.1.223.2:5000/v2/_catalog
curl http://10.1.223.2:5000/v2/library/nginx/tags/list五、用浏览器查看镜像库
compose 里的 web 服务用的是社区前端 konradkleine/docker-registry-frontend,只提供浏览功能(列出仓库、tag、镜像层信息),不能替代命令行推送。启动后访问:
http://服务器IP:8035它通过 ENV_DOCKER_REGISTRY_HOST 和 ENV_DOCKER_REGISTRY_PORT 去连 registry,所以填的必须是客户端能访问到的地址,而不是容器名 registry。这个项目已经多年没有更新,只把它当作一个查看工具使用即可,不要依赖它的写入或删除能力。
六、删除镜像与回收空间
Registry 没有一键删除仓库的接口,完整的删除流程分三步:
第一步,开启删除功能。把 compose 里注释掉的 REGISTRY_STORAGE_DELETE_ENABLED="true" 打开并重启服务,否则删除请求会直接被拒绝。
第二步,拿到 digest 再删除 manifest。tag 不能直接删,要先查出它对应的 Docker-Content-Digest:
curl -sS -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
-o /dev/null -w '%header{Docker-Content-Digest}' \
http://10.1.223.2:5000/v2/library/nginx/manifests/latest
# 用上一步得到的 digest 执行删除
curl -X DELETE http://10.1.223.2:5000/v2/library/nginx/manifests/sha256:xxxxxxxxxxxx注意按 digest 删除会影响所有指向该 digest 的 tag。
第三步,跑垃圾回收释放磁盘。删除 manifest 只是删掉引用,镜像层文件仍然留在磁盘上,必须执行 gc:
docker exec -it registry /bin/registry garbage-collect /etc/docker/registry/config.yml --delete-untaggedgc 期间应避免写入操作,生产环境建议先停掉推送。
七、常见问题
| 现象 | 原因与处理 |
|---|---|
http: server gave HTTP response to HTTPS client | 客户端没配 insecure-registries,或改完没重启 Docker |
push 报 denied: requested access to the resource is denied | 镜像名没有以 registry 地址开头,被当成 Docker Hub 的仓库推送了;重新 docker tag |
| 外部机器连不上 5000 | 检查 REGISTRY_HTTP_ADDR 是否为 0.0.0.0:5000,以及防火墙/安全组是否放行该端口 |
| 容器重建后镜像全没了 | ./data:/var/lib/registry 挂载缺失或路径写错,镜像实际写在了容器可写层里 |
| 删除接口返回 405 或无效 | 未开启 REGISTRY_STORAGE_DELETE_ENABLED=true |
| 删了镜像但磁盘没释放 | 还需要执行 registry garbage-collect |
| Web 前端 8035 打开是空白/报错 | ENV_DOCKER_REGISTRY_HOST / PORT 填成了容器名或与实际对外地址不符 |
附录:如果之后需要加认证
把上面的部署改成需要账号密码,主要是在 registry 的 environment 里补三项,并在宿主机生成一个 htpasswd 文件挂载进容器:
# 生成 htpasswd(bcrypt),输出到 ./auth/htpasswd
mkdir -p auth
docker run --rm --entrypoint htpasswd httpd:2 -Bbn your-username your-password > ./auth/htpasswd environment:
- REGISTRY_AUTH=htpasswd
- REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
volumes:
- ./data:/var/lib/registry
- ./auth:/auth加了认证之后,客户端推送前需要先登录,密码建议用管道传入,避免留在 shell 历史里:
echo 'your-password' | docker login --username your-username --password-stdin http://10.1.223.2:5000改完记得 docker-compose up -d 重建容器让新配置生效。
小结
无认证的开放式 Registry 部署很轻:一个 registry:2 容器加目录挂载,客户端配上 insecure-registries 就能推拉。使用中真正容易踩坑的是三件事——镜像名必须以仓库地址开头、明文 HTTP 必须靠 insecure-registries 放行(registry-mirrors 管不了它)、删除镜像需要「开启删除 + 删 manifest + 跑 gc」三步走。这套方案只适用于受控内网,公网环境请务必补上 HTTPS 与认证。
评论区