自建镜像仓库:registry 一条命令 + harbor 部署、授权、跨库同步与卸载

云仓库有免费额度、有网速、还有「镜像不能出机房」的合规要求。这篇把仓库建在自己的服务器上:先用一条命令起 registry,把 tag/push/pull 和 _catalog 接口跑通;再上 harbor,从离线包、harbor.yml 到 install.sh 装完十个容器,做用户、项目、角色授权,最后配一条跨机房同步规则并学会干净卸载。

本文概要:

项目 内容
文章主题 用 registry 与 harbor 自建 Docker 镜像仓库,含部署、授权、跨仓库同步与卸载
所属类别 云计算 → 容器技术 → 镜像仓库
知识关键字 registry · harbor · insecure-registries · registry-mirrors · _catalog · docker compose · 复制规则 · extra_hosts

主线一句:先跑通再谈管理。第一章两条命令起 registry,第二章打通推拉链路,第三、四章学会查和删。第五章换成带界面和权限的 harbor,第六章配同步,第七章卸载。

一、registry:一条命令起一个仓库

官方 registry 镜像自带一个完整仓库,跑起来就行:

bash 复制代码
# 宿主 80 → 容器 5000;/registry 挂出来,容器删了镜像还在
[root@docker ~]# docker container run -d -p 80:5000 --restart=always \
    --name registry -v /registry:/var/lib/registry registry
56f70abaf1d08f5cfc5c659c74c5922ecc9a77c528a59c5fefb08391582f6e55

六个参数各管一件事:

参数 含义 英文原意
-d 后台运行,不占终端 detach,分离
-p 80:5000 宿主 80 映射容器 5000,registry 在容器里监听 5000 publish,发布端口
--name registry 给容器起名,之后能用名字停删 name,名字
-v /registry:/var/lib/registry 宿主目录挂进容器,镜像数据落在宿主机 volume,卷
--restart=always Docker 重启后容器自己回来 restart,重启
registry 镜像名,官方最小仓库镜像 ---

--restart=always 让它在 Docker 重启后自己回来,-v /registry:/var/lib/registry 是关键------不挂这个目录,容器一删,所有镜像跟着没。

1.1 先让 Docker 允许走 http

Docker 1.9 之后默认只认 https,访问 http 仓库会被拒绝。内部仓库没必要为它签证书,两端都放开即可:

bash 复制代码
[root@docker ~]# tee /etc/docker/daemon.json <<-'EOF'
{
  "insecure-registries": ["registry.zhb.cloud"],
  "registry-mirrors": ["https://<你的加速器地址>.mirror.swr.myhuaweicloud.com"]
}
EOF
[root@docker ~]# systemctl restart docker

两件事:insecure-registries 明确允许这个域名走 http(可以带端口,registry.zhb.cloud:5000),registry-mirrors 是拉公共镜像的加速器------两者互不相干,别写混。

域名要能解析到本机,否则 Docker 连不上:

bash 复制代码
[root@docker ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain
10.1.8.10 registry.zhb.cloud

⚠️ 注意 :insecure-registries 只写域名(可带端口)就够,官方示例是 "myregistrydomain.com:5000"。网上不少教程写成 "http://registry.zhb.cloud",Docker 也能容忍,但一旦带上路径或多余斜杠就会解析失败,重启 docker 直接报错。

二、把镜像推上去、拉下来

registry 没有「组织」概念,镜像名的第二段随便写,仓库里就按这个路径存。

bash 复制代码
# 三类镜像各推一个,路径按用途分类
[root@docker ~]# docker tag busybox registry.zhb.cloud/tools/busybox:latest
[root@docker ~]# docker push registry.zhb.cloud/tools/busybox:latest

[root@docker ~]# docker tag wordpress registry.zhb.cloud/webapp/wordpress:latest
[root@docker ~]# docker push registry.zhb.cloud/webapp/wordpress:latest

[root@docker ~]# docker tag mysql registry.zhb.cloud/db/mysql:latest
[root@docker ~]# docker tag mysql registry.zhb.cloud/db/mysql:test
[root@docker ~]# docker push registry.zhb.cloud/db/mysql:latest
[root@docker ~]# docker push registry.zhb.cloud/db/mysql:test

同一个镜像贴两个标签,就是仓库里两个版本。test 和 latest 指向同一份内容,但地址不同------回滚时切标签比重新推一份快得多。

bash 复制代码
# 拉回来,速度是内网的速度
[root@docker ~]# docker pull registry.zhb.cloud/webapp/wordpress:latest
[root@docker ~]# docker pull registry.zhb.cloud/db/mysql:latest

2.1 让 docker pull db/mysql 也命中本地仓库

平时敲 docker pull mysql,Docker 会去 docker.io。把私有仓库塞进 registry-mirrors 列表,短名字也能优先命中本地:

bash 复制代码
[root@docker ~]# tee /etc/docker/daemon.json <<-'EOF'
{
  "insecure-registries": ["registry.zhb.cloud"],
  "registry-mirrors": ["http://registry.zhb.cloud","https://<你的加速器地址>.mirror.swr.myhuaweicloud.com"]
}
EOF
[root@docker ~]# systemctl restart docker

[root@docker ~]# docker pull db/mysql
Status: Downloaded newer image for db/mysql:latest
docker.io/db/mysql:latest

回显里写着 docker.io/db/mysql:latest,但下载是从列表里第一个地址拿的,所以几乎瞬间完成。这是利用 mirror 的兜底行为的加速手段,不是权限控制------公开仓库里的同名镜像依然能被人拉到,别拿它当访问限制。

三、不装界面,用 curl 查仓库内容

registry 自带一套 HTTP API,查目录、查标签两条就够:

端点 作用 说明
/v2/_catalog 列出所有仓库 registry 本身不支持分页过滤,仓库多了会很慢
/v2/<名字>/tags/list 列出某个仓库的标签 名字带斜杠要原样写,如 db/mysql
/v2/ 探活 返回 {} 说明服务正常,401 说明要认证
bash 复制代码
[root@docker ~]# curl -s http://registry.zhb.cloud/v2/_catalog
{"repositories":["db/mysql","tools/busybox","webapp/wordpress"]}

# 装个 yajl 让 JSON 好看点,排查时省眼力
[root@docker ~]# yum install -y yajl
[root@docker ~]# curl -s http://registry.zhb.cloud/v2/db/mysql/tags/list | json_reformat
{
    "name": "db/mysql",
    "tags": [
        "latest",
        "test"
    ]
}

⚠️ 注意 :registry 的 API 是明文 http 且默认无认证(除非另配 htpasswd)。任何人只要能访问 80 端口,就能列出你所有镜像并拉走。这台机器必须只在内网,公网部署请直接上 harbor。

四、删镜像:registry 不给你 rmi

docker rmi 只删本地,仓库里的 blob 和清单还在。registry 的删除要走它自带的脚本:

bash 复制代码
# 下载社区维护的清理脚本
[root@docker ~]# curl -s https://raw.githubusercontent.com/burnettk/delete-docker-registry-image/master/delete_docker_registry_image.py | sudo tee /usr/local/bin/delete_docker_registry_image >/dev/null
[root@docker ~]# chmod a+x /usr/local/bin/delete_docker_registry_image

# 告诉脚本仓库数据在哪(宿主上挂的那个目录)
[root@docker ~]# export REGISTRY_DATA_DIR=/registry/docker/registry/v2
参数 含义 英文原意
-i / --image 要删的镜像路径,如 webapp/wordpress image,镜像
-n / --dry-run 只打印要删哪些文件,不动手 dry run,空跑
-p / --prune 顺带清掉无人引用的 blob prune,修剪
-u / --untagged 删掉该镜像下所有没有标签的 blob untagged,未打标签
-v / --verbose 打印详细过程 verbose,啰嗦
bash 复制代码
# 先空跑看一眼,确认无误再真删
[root@docker ~]# delete_docker_registry_image -i webapp/wordpress -n
[root@docker ~]# delete_docker_registry_image -i webapp/wordpress
INFO  Deleting /data/docker/registry/v2/blobs/sha256/46/4676067592f277e1723e4ab4c588603df0b3dea762e22c354f7ada29b391cf10
INFO  Deleting /data/docker/registry/v2/repositories/webapp/wordpress

[root@docker ~]# curl -s http://registry.zhb.cloud/v2/_catalog | json_reformat
{
    "repositories": [
        "db/mysql",
        "tools/busybox"
    ]
}

删完目录就空了。-n 是这条命令唯一的安全带,养成先空跑的习惯。

五、harbor:带界面、带权限的仓库

registry 只能存镜像,谁推的、谁拉的、能不能删,一概不知道。harbor 把这几件事补齐了。

能力 registry harbor
网页界面 无 有,能浏览仓库、看标签、看日志
用户与权限 无 用户、项目、五种角色
操作审计 无 记录每次推拉删
镜像扫描 无 集成 Trivy 漏洞扫描
部署方式 一个容器 十个容器,靠 docker compose 编排
镜像清理 手写脚本 界面里配保留策略

5.1 装:离线包 + 改三处配置 + install.sh

bash 复制代码
[root@harbor ~]# wget https://github.com/goharbor/harbor/releases/download/v2.10.2/harbor-offline-installer-v2.10.2.tgz
[root@harbor ~]# tar -xf harbor-offline-installer-v2.10.2.tgz
[root@harbor ~]# cd harbor/
[root@harbor harbor]# ls
common.sh  harbor.v2.10.2.tar.gz  harbor.yml.tmpl  install.sh  LICENSE  prepare

[root@harbor harbor]# cp harbor.yml.tmpl harbor.yml

harbor.yml.tmpl 是模板,复制一份改成 harbor.yml 再改,别直接动模板。要改的只有三处:

yaml 复制代码
# 1) 访问域名,必须和客户端 /etc/hosts 里的一致
hostname: registry.zhb.cloud

# 2) https 段整段注释掉,内部用 http
# https:
#   port: 443
#   certificate: /your/certificate/path
#   private_key: /your/private/key/path

# 3) 管理员初始密码,上线前一定要改
harbor_admin_password: Zhb@123
bash 复制代码
# 本机 hosts 也要有解析记录
[root@harbor ~]# echo "10.1.8.10 registry.zhb.cloud" >> /etc/hosts

[root@harbor harbor]# ./install.sh
[Step 0]: checking if docker is installed ...
Note: docker version: 26.1.3
[Step 1]: checking docker-compose is installed ...
Note: Docker Compose version v2.27.0
[Step 2]: loading Harbor images ...
Loaded image: goharbor/prepare:v2.10.2
[Step 3]: preparing environment ...
[Step 4]: preparing harbor configs ...
Generated configuration file: /compose_location/docker-compose.yml
[Step 5]: starting Harbor ...
[+] Running 10/10
 ✔ Container harbor-log         Started
 ✔ Container registryctl        Started
 ✔ Container harbor-db          Started
 ✔ Container redis              Started
 ✔ Container registry           Started
 ✔ Container harbor-portal      Started
 ✔ Container harbor-core        Started
 ✔ Container harbor-jobservice  Started
 ✔ Container nginx              Started
✔ ----Harbor has been installed and started successfully.----

十步输出里要盯的是 [Step 4] 和 [Step 5]:前者生成 docker-compose.yml,后者把十个容器拉起来。中间出现 WARNING: HTTP protocol is insecure 属正常,因为你确实关了 https。

bash 复制代码
# 十个容器全 healthy 才叫装好
[root@harbor ~]# docker ps --format='{{ .Names }}\t{{ .Status }}'
harbor-jobservice	Up 28 minutes (healthy)
nginx	Up 28 minutes (healthy)
harbor-core	Up 28 minutes (healthy)
redis	Up 28 minutes (healthy)
harbor-db	Up 28 minutes (healthy)
registry	Up 28 minutes (healthy)
harbor-portal	Up 28 minutes (healthy)
registryctl	Up 28 minutes (healthy)
harbor-log	Up 28 minutes (healthy)

5.2 注册成系统服务

install.sh 起的容器只是「跑起来了」,机器重启不会自己回来。补一个 unit:

bash 复制代码
# 把整个目录搬到 /etc,配置和 compose 文件跟着走
[root@harbor ~]# cp -a harbor /etc
[root@harbor ~]# vim /etc/systemd/system/harbor.service
ini 复制代码
[Unit]
Description=Harbor Docker Registry
After=docker.service network-online.target
Requires=docker.service network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/etc/harbor
ExecStart=/usr/libexec/docker/cli-plugins/docker-compose up -d
ExecStop=/usr/libexec/docker/cli-plugins/docker-compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
bash 复制代码
[root@harbor ~]# systemctl daemon-reload
[root@harbor ~]# systemctl enable harbor

Type=oneshot + RemainAfterExit=yes 是这套写法的关键:docker compose up -d 起完就退出,oneshot 才不会把它当成「服务挂了」反复重启。

5.3 网页端:建用户、建项目、授权

浏览器打开 http://registry.zhb.cloud/:

用 admin 和 harbor.yml 里那个密码登进去。先建普通用户,再建项目,最后把用户加进项目------顺序反过来会没地方授权。

项目名就是镜像名里的第二段,公开项目任何人可拉、私有项目要授权。配额按存储算,实验里 10GiB 足够。

授权在项目的「成员」页里加:

五种角色的边界很清楚:

角色 能做什么
项目管理员 项目内全部权限,含加成员、改配置、删仓库
维护人员 推拉镜像、扫描、看日志,不能管成员
开发者 推拉镜像,不能删仓库、不能扫描
访客 只读,只能拉
受限访客 只读且看不到日志

5.4 客户端登录:凭据存哪、怎么改

bash 复制代码
[root@docker ~]# tee /etc/docker/daemon.json <<-'EOF'
{
  "insecure-registries": ["registry.zhb.cloud"],
  "registry-mirrors": ["http://registry.zhb.cloud","https://<你的加速器地址>.mirror.swr.myhuaweicloud.com"]
}
EOF
[root@docker ~]# systemctl restart docker

⚠️ 注意 :如果 harbor 和客户端是同一台机器,systemctl restart docker 会把 harbor 的十个容器一起停掉。重启完回 harbor 目录再跑一次 ./install.sh,容器才会回来。

bash 复制代码
[root@docker ~]# docker login registry.zhb.cloud
Username: zhb
Password:
Login Succeeded

# 凭据落在用户家目录,内容是 base64
[root@docker ~]# cat ~/.docker/config.json
{
	"auths": {
		"registry.zhb.cloud": {
			"auth": "emhiOlpoYkAxMjM="
		}
	}
}

那一串 emhiOlpoYkAxMjM= 就是 用户名:密码 的 base64,等于明文,只是看着不像:

bash 复制代码
[root@docker ~]# echo -n 'zhb:Zhb@123' | base64
emhiOlpoYkAxMjM=

镜像地址格式是 服务器/项目/镜像:标签,推之前把项目段写对:

bash 复制代码
# 推到 cloud 项目下
[root@docker ~]# docker tag mysql registry.zhb.cloud/cloud/mysql:latest
[root@docker ~]# docker push registry.zhb.cloud/cloud/mysql:latest
[root@docker ~]# docker push registry.zhb.cloud/cloud/wordpress:latest

# 拉
[root@docker ~]# docker pull registry.zhb.cloud/cloud/mysql:latest

# 查仓库清单要管理员凭据
[root@docker ~]# curl -s http://registry.zhb.cloud/v2/_catalog -u admin:Zhb@123
{"repositories":["cloud/mysql"]}

和裸 registry 最大的区别就在这两行 curl 上:带了 -u,说明这套 API 后面已经有了认证。

5.5 跨仓库同步:把镜像搬到另一台 harbor

场景是两个机房各有一套 harbor,需要把镜像从 A 同步到 B。harbor 把这件事拆成「仓库管理」和「复制规则」两步:前者告诉它目标在哪,后者告诉它搬什么。

第一步:仓库管理 → 新建目标

字段 填什么
提供者 Harbor(同步 harbor 就选它,别的 registry 选 Docker Registry)
目标名 自定义,只给运维看,如 harbor-prod
目标 URL 远端 harbor 的访问地址
访问 ID / 密码 远端 harbor 的管理员账号
验证远程证书 目标用自签证书时取消勾选,否则连不上

⚠️ 注意 :如果远端 harbor 的 hostname 写的是 FQDN(如 hub.zhb.cloud),本机必须能解析这个名字,否则同步任务会卡在「连接失败」。要么加 DNS,要么在本地 harbor 上补 extra_hosts。

yaml 复制代码
# /etc/harbor/docker-compose.override.yml(本地 harbor 上)
services:
  core:
    extra_hosts:
      - "hub.zhb.cloud:192.168.50.201"
  jobservice:
    extra_hosts:
      - "hub.zhb.cloud:192.168.50.201"

第二步:复制管理 → 新建规则

字段 取值 说明
名称 sync-all 必填,运维识别用,不能重名
复制模式 Pull-based 由当前 harbor 主动去源拉;Push-based 是源端主动推
源仓库 harbor-prod 上一步建的目标
源资源过滤器 留空 / 全部 留空即同步所有项目、所有标签
目标名称空间 留空 留空则与源端同名;填了就全塞进这一个项目
仓库扁平化 替换 1 级 去掉路径第一层,路径变了业务拉取地址要跟着改
触发模式 手动 也可定时(Cron)或事件驱动(源端一 push 就同步)

第三步:选中规则点「复制」,看任务结果

手动模式保存后不会自己跑,必须点「复制」。任务列表里状态是 Succeeded 才算搬完,Failed 就点进去看报错------九成是网络不通或证书校验没关。

六、卸载:四步清干净

bash 复制代码
# 1. 停容器
[root@harbor harbor]# docker compose down

# 2. 删镜像
[root@harbor harbor]# docker images | grep harbor | awk '{print $1":"$2}' | xargs docker rmi

# 3. 删数据目录(镜像、数据库、密钥都在这)
[root@harbor harbor]# rm -fr /data

# 4. 删安装包
[root@harbor ~]# rm -fr harbor harbor-offline-installer-v2.10.2.tgz

第 3 步是重点:/data 里是全部镜像和 harbor 的数据库,删了就真没了。只想清理空间、还想留配置的,跳过第 3 步。

顺手把 systemd 的残留也清掉,否则下次装会撞名:

bash 复制代码
[root@harbor ~]# systemctl disable harbor
[root@harbor ~]# rm -f /etc/systemd/system/harbor.service
[root@harbor ~]# systemctl daemon-reload

附:命令英文全称速查表

命令 / 缩写 英文全称 / 原意 作用
registry registry 官方最小镜像仓库
harbor harbor 企业级镜像仓库,含界面与权限
login / logout login / logout 登录、退出仓库
push / pull push / pull 推、拉镜像
compose compose 按 compose 文件编排多容器
_catalog catalog 仓库清单 API 端点
replication replication 复制,harbor 的跨仓库同步
extra_hosts extra hosts compose 里给容器额外写 hosts
Trivy Trivy 镜像漏洞扫描组件

📚 参考资料(官方文档)

总结 :内网要一个能用的仓库,registry 一个容器 + 一份 daemon.json 就够,代价是没有权限、日志和清理------所以它只适合实验或严格内网。要给人用、要给团队用,直接上 harbor:harbor.yml 改域名、关 https、改密码三处,install.sh 一跑,十个容器起来,再配用户、项目、角色,最后按需配复制规则把镜像搬到另一个机房。记住两个坑:重启 docker 会带走同机的 harbor,删镜像必须用脚本而不是 docker rmi。


📚 同系列 · Docker 从镜像到容器(其余篇目见博客主页)

blog.csdn.net/nbsxl

  • 上一篇:《镜像推上云:华为云 SWR 与阿里云 ACR 的建组织、加速器、上传与公开》
  • 相关篇:《别再 docker commit 造镜像:Dockerfile 十六个指令与 docker build 实操》
  • 相关篇:《Docker 镜像管理:十个命令走完找拿看标删搬推》

💬 你的 harbor 装完打不开首页,一般是 nginx 容器没起来。docker compose ps 看一眼,把 harbor-log 的日志贴出来,一起定位是端口占用还是证书配置。

相关推荐
wz97541 天前
Docker 从 systemd 托管容器、overlay2 存储驱动到 Compose 多容器实战
spring cloud·docker·eureka
新*1 天前
ES 7.17 三节点 Docker 集群接入 IK 自定义词典:一次从丢插件到热更新的完整排障
运维·elasticsearch·docker·中文分词·ik分词
小白的码BUG之路1 天前
Jenkins -- gitee+docker+harbor+k8s 部署项目
docker·gitee·jenkins
AlfredZhao1 天前
Docker Compose 平滑切换环境变量文件,容器配置不残留
docker
duansamve2 天前
Python3+Flask+PostgreSQL18+Vue3项目生产环境用docker如何部署?
python·docker·flask
xbzb2 天前
从零容器化部署 ECShop:Harbor 私有仓库 + 自定义 PHP 镜像 + LNMP 三容器
容器·php·harbor·镜像·仓库·ecshop
那明眸似水2 天前
云原生项目三:Docker秒级启动NextCloud个人网盘存储办公系统
linux·运维·docker·云原生·容器
知已难寻2 天前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
xbzb3 天前
镜像到底存在哪:/var/lib/docker 的四级映射与 daemon.json 配置
docker·容器·json·镜像·daemon.json·四级