适用对象:只有一台服务器(或一台测试虚拟机),想把私有镜像仓库跑起来。
环境约定:单台机器 192.168.28.100(CentOS 7),Docker CE 20.10.18 + docker compose v2 + Harbor v2.9.0(HTTP)。
仓库地址 192.168.28.100,账号 admin / Harbor12345。
预计耗时:装 Docker 10 分钟;装 Harbor 15 分钟 + 下载 750MB 安装包。
版本:v1.0,整理日期:2026-11-07(配套脚本:install-harbor-single.sh)。
复制约定:本文命令统一缩进 6 个空格只为排版好看;但 heredoc 段落(cat > 文件 <<'EOF' ... EOF)必须整段顶格,EOF 前不能有空格。
目录
- 一、单机版和双机版到底差在哪
- 二、先确认三件事:端口、磁盘、内存
- 三、快速路径:脚本一把梭(3 分钟)
- 四、分步详解(想搞懂每一步就看这里)
- 五、单机特有的四个坑
- 六、本机自测:push 一个镜像再拉下来
- 七、验收清单:10 项
- 八、精简报错表:10 条
- 九、日常维护(单机要特别注意重启顺序)
- 附录 A:一键脚本全文
- 附录 B:彻底卸载
一、单机版和双机版到底差在哪
| 项目 | 双机版 | 单机版 |
|---|---|---|
| 机器数量 | 2 台 | 1 台 |
| 各机器职责 | 100 构建 + Harbor;128 只负责拉取运行 | 同一台:Docker + Harbor + 业务 |
| 网络要求 | 两台互通、IP 固定 | 只需一个固定 IP |
| 资源竞争 | 无 | 端口 / 磁盘 / 内存全挤在一起 |
| 重启影响面 | 只影响一端 | 仓库和业务一起中断 |
| 典型用途 | 接近生产的分发链路 | 开发自测、小团队、离线交付 |
一句话:单机版不难,难的是"挤"。装的过程和双机版几乎一样,真正的风险集中在第五节讲的四个坑上,先看那一节能省很多时间。
单机也能被局域网其他机器访问,前提是 hostname 写本机 IP 而不是 localhost(第五章坑 4 会讲为什么这条最容易犯)。
资源要求(单机版要留出业务余量,比双机版更保守):
| 项目 | 最低 | 推荐 | 检查命令 |
|---|---|---|---|
| CPU | 2 核 | 4 核 | nproc |
| 内存 | 4 GB | 8 GB | free -h |
| /data | 40 GB 可用 | 100 GB | df -h /data |
| 端口 | 80 空闲 | 80 空闲 | `ss -lntp |
为什么推荐 8GB:Harbor 的 9 个容器常驻约 1.5~2.5GB。4GB 的机器装完 Harbor 就基本没余量了,再跑数据库或 Java 业务,很容易触发 OOM Killer,表现是 harbor-core 反复重启,网页时好时坏。
二、先确认三件事:端口、磁盘、内存
单机部署前只做这三条检查,比背十条命令有用。
2.1 80 端口有没有被占(最常见的问题)
bash
ss -lntp | grep :80
没有输出 = 空闲,HARBOR_PORT 用 80。
有输出(nginx / httpd / tomcat 占了)= 两种选择:
- 选择 A:停掉占用端口的服务(只适合这台机器专用于仓库)
- 选择 B:Harbor 改用 8080(推荐,业务不受影响)
选 B 的话,记住一句话:所有地址都要带 :8080,一共 3 处要改,文档里凡是出现地址的地方都已经标注,照抄即可。
2.2 磁盘够不够
bash
df -h /data 2>/dev/null || df -h /
/data 不存在就用根分区的数字。要求 40GB 以上可用。
单机版特别注意:业务日志、容器日志也在这块盘上,Harbor 再吃掉几十 G,很容易把盘用到 100%。盘满之后 push 直接失败,而且 Harbor 可能写坏配置。
如果有一块空闲盘,建议单独挂到 /data(第五章坑 2 给了完整做法)。
2.3 内存够不够
bash
free -h
看 available 那一列。小于 3GB 的,建议先加内存或加 swap 再装。加 swap(临时缓解,性能不如加内存):
bash
dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
2.4 顺手把两个基础项做掉
bash
# 防火墙和 SELinux(实验环境直接关,生产见 Docker04 第二章)
systemctl stop firewalld && systemctl disable firewalld
setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
时间必须准,否则登录报 401 而密码明明是对的
yum install -y chrony && systemctl enable --now chronyd
date
确认 IP(后面所有配置都用它):
bash
ip addr | grep "inet "
三、快速路径:脚本一把梭(3 分钟)
如果只是想赶紧跑起来,跳过第四、五章的讲解,直接执行下面 4 条。
3.1 把脚本传上去(在你自己的电脑上执行)
bash
scp install-harbor-single.sh root@192.168.28.100:/opt/
脚本就在同一目录下,150 行,附录 A 有全文,可以先看再跑。
3.2 按你的实际情况改 4 个变量(不改就用默认值)
bash
cd /opt
vim install-harbor-single.sh
# 文件开头这几行:
# HARBOR_IP=192.168.28.100 ← 改成你的真实 IP
# HARBOR_PORT=80 ← 80 被占就改 8080
# HARBOR_PASS=Harbor12345 ← 管理员密码
# MIRROR="" ← 有阿里云加速器就填进去
3.3 执行
bash
bash install-harbor-single.sh
脚本依次做 9 件事,每步都会打印 1/9 这样的进度:
text
[1/9] 检查系统、IP、端口、磁盘
[2/9] 修复 CentOS 7 的 yum 源(指向 vault 归档站)
[3/9] 安装 docker-ce 20.10.18
[4/9] 写 daemon.json(加速器 + 信任本机仓库地址)
[5/9] 安装 docker compose v2 插件
[6/9] 下载并解压 Harbor v2.9.0(约 750MB,最慢的一步)
[7/9] 改 harbor.yml 的 4 个关键项
[8/9] ./prepare && ./install.sh
[9/9] 健康检查
中途断了可以重跑,脚本会跳过已完成的步骤(Harbor 目录已存在时会先问你,不会直接覆盖数据)。
3.4 看到这行就成功了
text
✔ ----Harbor has been installed and started successfully.----
浏览器打开 http://192.168.28.100:端口,admin / 你的密码。
想搞懂脚本到底做了什么、出问题怎么改,继续看第四、五章。
四、分步详解(想搞懂每一步就看这里)
4.1 修 yum 源:CentOS 7 已停止维护,这步跳不过
不修的话,装 docker 会报 "Cannot find a valid baseurl for repo: base/7/x86_64"。整段顶格粘贴:
bash
cat > /etc/yum.repos.d/CentOS-Base.repo <<'EOF'
[base]
name=CentOS-7.9.2009 - Base
baseurl=https://vault.centos.org/7.9.2009/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
enabled=1
[updates]
name=CentOS-7.9.2009 - Updates
baseurl=https://vault.centos.org/7.9.2009/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
enabled=1
[extras]
name=CentOS-7.9.2009 - Extras
baseurl=https://vault.centos.org/7.9.2009/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
enabled=1
EOF
国内访问慢就换阿里云归档站:
bash
sed -i 's#https://vault.centos.org#https://mirrors.aliyun.com/centos-vault#g' \
/etc/yum.repos.d/CentOS-Base.repo
yum clean all && yum makecache
yum repolist # 应看到 base/extras/updates 三行
4.2 装 Docker(锁 20.10.18,别装 latest)
bash
yum install -y yum-utils
yum-config-manager --add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y docker-ce-20.10.18 docker-ce-cli-20.10.18 containerd.io
报 podman 冲突时加 --allowerasing:
bash
yum install -y docker-ce-20.10.18 docker-ce-cli-20.10.18 \
containerd.io --allowerasing
bash
systemctl enable --now docker
docker version # 必须同时有 Client 和 Server 两段
docker run hello-world # 出现 Hello from Docker! 才算通
4.3 写 daemon.json(单机版这里有个容易写错的地方)
为什么单机也要配 insecure-registries:docker push 是 docker 守护进程去连仓库,走的是 HTTP 协议。不把这个地址加白名单,即使是本机推送,也会报 "http: server gave HTTP response to HTTPS client"。
关键:地址必须和你将来 docker login 用的地址一字不差。用 IP 就全用 IP,用 8080 端口就带上 :8080。
整段顶格粘贴(端口是 80 就写 "192.168.28.100",是 8080 就写 "192.168.28.100:8080"):
bash
cat > /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": ["https://你的ID.mirror.aliyuncs.com"],
"insecure-registries": ["192.168.28.100"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
EOF
没有加速器就把 registry-mirrors 那一行删掉(注意别留下多余的逗号)。log-opts 是单机版必配项:日志不轮转,几十个容器能把盘写满。
校验 + 生效 + 验证:
bash
python -m json.tool /etc/docker/daemon.json
systemctl daemon-reload && systemctl restart docker
docker info | grep -A2 "Insecure Registries" # 应出现 192.168.28.100/32
4.4 装 docker compose v2
Harbor 2.9 只认 v2(docker compose 空格形式),老教程的 docker-compose(横杠,v1)不认。
bash
yum install -y docker-compose-plugin
docker compose version # v2.x 即可
没这个包时手动装二进制:
bash
mkdir -p /usr/local/lib/docker/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-linux-x86_64 \
-o /usr/local/lib/docker/cli-plugins/docker-compose
chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
ln -sf /usr/local/lib/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose
4.5 下载并解压 Harbor
bash
cd /opt
wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-offline-installer-v2.9.0.tgz
ls -lh harbor-offline-installer-v2.9.0.tgz # 小于 700MB 说明没下完
tar -zxvf harbor-offline-installer-v2.9.0.tgz -C /opt
cd /opt/harbor && ls
用 offline 版的原因:9 个镜像都打在包里,装的时候直接 load,不联网。online 版只有 10KB,但安装时要现拉几百 MB,国内常卡在中途。
下载太慢的替代:用能上 GitHub 的电脑下载,再 scp 上传到 /opt。wget -c 支持断点续传,断了接着下。
4.6 改 harbor.yml(只改 4 处)
bash
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
yaml
# 1) hostname → 本机 IP,绝对不能写 localhost(见坑 4)
hostname: 192.168.28.100
2) http.port → 80,或按 2.1 的结论写 8080
http:
port: 80
3) https 段 → 保持注释,8 行全带
https:
port: 443
certificate: /your/certificate/path
private_key: /your/private/key/path
取消注释会去找证书文件,找不到直接报
"Error happened in config validation",装不下去
4) 管理员密码 → 改成自己的
harbor_admin_password: Harbor12345
其余字段保持默认,知道含义即可:
yaml
data_volume: /data # 镜像和数据库数据都在这
database:
password: root123 # 内部 PostgreSQL 密码,不是登录密码
三条自查:
bash
grep -n "^hostname" harbor.yml # 是 IP
grep -n "^ port" harbor.yml # 80 或 8080
grep -n "^# https" harbor.yml # 有 #
4.7 预检 + 安装
bash
./prepare
成功标志:✔ ----Harbor has been prepared successfully.
改了 harbor.yml 就必须重跑这条,否则改动不生效。
bash
./install.sh
3~8 分钟,屏幕滚动大量解压信息属正常。成功标志:
text
✔ ----Harbor has been installed and started successfully.----
4.8 验证
bash
cd /opt/harbor
docker compose ps # 9 个容器都应是 Up (healthy)
bash
curl -I http://192.168.28.100 # 80 端口的情况
curl -I http://192.168.28.100:8080 # 8080 的情况
期望 200 或 302。刚起完等 1~2 分钟,core/jobservice 显示 Up (health: starting) 是正常的。
五、单机特有的四个坑
坑 1:端口冲突(占了 80 还想用 80)
单机上往往已经有 nginx、tomcat、GitLab 在跑,Harbor 默认抢 80。
决策:能停就停,不能停就改 8080。改 8080 一共 3 处,缺一必错:
/opt/harbor/harbor.yml→http.port: 8080/etc/docker/daemon.json→"192.168.28.100:8080"- 所有命令和浏览器地址都带
:8080
改完必须重新生成配置并重建容器(restart 不会重建端口映射):
bash
cd /opt/harbor
./prepare
docker compose down
docker compose up -d
坑 2:磁盘竞争(业务日志 + 镜像层抢同一块盘)
最省事的做法是给 /data 单独挂一块盘:
bash
fdisk -l # 找到新盘,假设 /dev/sdb
mkfs.xfs /dev/sdb
mkdir -p /data
mount /dev/sdb /data
echo '/dev/sdb /data xfs defaults 0 0' >> /etc/fstab
df -h /data
Harbor 已经装好、想把 /data 迁到新盘(数据量大时用):
bash
cd /opt/harbor && docker compose stop
mv /data /data.old
mkdir /data && mount /dev/sdb /data
cp -a /data.old/. /data/
docker compose start
# 确认网页和镜像都正常后再删 /data.old
日常盯盘:
bash
df -h /data
du -sh /data/* | sort -rh | head
盘用到 90% 就该清理了,别等到 100%,满了之后 Harbor 会写坏配置,恢复比重装还麻烦。
坑 3:内存竞争(容器被 OOM Killer 干掉)
现象:harbor-core 反复重启,网页一会儿能开一会儿 502,日志里没有明显报错。查一下就能确认:
bash
dmesg | grep -i "killed process"
free -h
看到 "Killed process ... (harbor_core)" 就是内存不够。
处理(按代价从低到高):
- 加 swap 应急(2.3 的命令),能顶一阵
- 把业务容器限制内存:
docker run加--memory=512m - 给这台机器加内存,或把 Harbor 单独放一台
单机部署时,harbor.yml 里的 no_proxy、代理之类的配置不用管,真正要控制的是"别让业务容器和 Harbor 抢内存"。
坑 4:hostname 写成 localhost 或 127.0.0.1(官方明确不建议)
后果比想象严重:
- 镜像名变成
localhost/xxx,别人根本拉不到 - 本机用 127.0.0.1 访问,和 hostname 不一致,登录可能 401
- insecure-registries 是精确匹配,写了 IP 就不认 localhost
规则:hostname 写本机 IP,则后续所有命令(login/tag/push/pull、浏览器)都用同一个 IP。哪怕只是本机自测,也别图省事写 127.0.0.1。
已经装错了要改:
bash
cd /opt/harbor
vim harbor.yml # hostname 改成 IP
./prepare
docker compose down
docker compose up -d
六、本机自测:push 一个镜像再拉下来
单机版的好处是能在同一台机器上闭环验证,10 分钟就能确认仓库真的能用。
6.1 网页建项目
浏览器打开 http://192.168.28.100(或 :8080),admin / 你的密码。
项目 → 新建项目 → 名称 ruoyi-project → 访问级别:私有 → 确定。
6.2 登录(地址必须和 hostname 一致)
bash
docker login 192.168.28.100 -u admin -p Harbor12345
# 8080 的情况:docker login 192.168.28.100:8080 -u admin -p Harbor12345
期望 Login Succeeded。
6.3 打标签并推送(命名必须三段)
bash
docker pull nginx:1.25.3
docker tag nginx:1.25.3 192.168.28.100/ruoyi-project/nginx:1.25.3
docker push 192.168.28.100/ruoyi-project/nginx:1.25.3
格式:<仓库地址>/<项目名>/<镜像名>:<标签>,少一段就被拒。
成功标志:最后出现 "1.25.3: digest: sha256:..."。
回网页 → ruoyi-project → 仓库,能看到 nginx 和 1.25.3 标签。
6.4 删掉本地镜像,再从仓库拉回来(验证真的存进去了)
bash
docker rmi 192.168.28.100/ruoyi-project/nginx:1.25.3
docker rmi nginx:1.25.3
docker pull 192.168.28.100/ruoyi-project/nginx:1.25.3
docker run -d --name web -p 8081:80 192.168.28.100/ruoyi-project/nginx:1.25.3
curl -I http://127.0.0.1:8081
期望 HTTP/1.1 200 OK。
注意 6.4 里的 127.0.0.1:8081 是浏览器访问容器,和 Harbor 地址无关,别因为这里出现 127.0.0.1 就把 4.3 的配置也改成 127.0.0.1。
6.5 局域网其他机器要用怎么办
只要满足两条,其他机器就能直接拉:
- hostname 是 IP(不是 localhost)
- 对方机器的 daemon.json 的 insecure-registries 里加上这个地址
对方机器上:
bash
# 编辑 /etc/docker/daemon.json,加入
"insecure-registries": ["192.168.28.100"]
systemctl daemon-reload && systemctl restart docker
docker login 192.168.28.100 -u admin -p Harbor12345
docker pull 192.168.28.100/ruoyi-project/nginx:1.25.3
七、验收清单:10 项
-
docker --version→ 20.10.18 -
systemctl is-enabled docker→ enabled -
docker info | grep -A2 "Insecure"→ 含本机 IP(有端口就带端口) -
docker compose version→ v2.x -
cd /opt/harbor && docker compose ps→ 9 个 Up (healthy) -
curl -I http://IP[:端口]→ 200 或 302 - 浏览器能登录 admin
- 项目 ruoyi-project 存在,能 push 一个镜像
- 删掉本地镜像后能重新 pull 回来
-
df -h /data→ 使用率 < 90%
八、精简报错表:10 条
【1】docker: command not found
→ 没装完或没重登:rpm -qa | grep docker;exec bash 重进会话。
【2】Cannot connect to the Docker daemon at unix:///var/run/docker.sock
→ systemctl start docker;仍失败看 journalctl -u docker -n 50。
【3】Job for docker.service failed / 启动 Docker 失败
→ 九成是 daemon.json 语法错:python -m json.tool /etc/docker/daemon.json。改不好就先 mv /etc/docker/daemon.json /tmp/ 让 docker 用默认配置起来。
【4】http: server gave HTTP response to HTTPS client
→ insecure-registries 没配 / 配了没重启 / 地址与端口不一致。docker info | grep -A2 Insecure 核对,注意 :8080 不能漏。
【5】denied: requested access to the resource is denied
→ 没登录;或私有项目里当前账号没权限;或镜像名少写项目名。
【6】unauthorized: authentication required
→ 密码错、凭据过期、或系统时间不准(date 对比一下)。
【7】./prepare 报 Error happened in config validation
→ harbor.yml 的 https 段没注释,或 hostname 还是 reg.mydomain.com。
【8】./install.sh 报 docker compose: command not found
→ 装的是 v1 的 docker-compose,Harbor 2.9 只认 v2:yum install -y docker-compose-plugin。
【9】网页 502 / 容器反复重启
→ docker compose ps 看谁不健康 → docker compose logs --tail 50 core。若同时 free -h 内存见底,就是 OOM(坑 3)。
【10】no space left on device(push 时)
→ df -h /data;停服跑 GC(第九章);注意盘满时不要执行 prepare/install,会写坏配置。
九、日常维护(单机要特别注意重启顺序)
9.1 启停(必须在 /opt/harbor 目录下)
bash
cd /opt/harbor
docker compose ps
docker compose stop # 停仓库(业务容器不受影响)
docker compose start
docker compose restart
docker compose down # 删容器和网络,/data 数据不动
绝对不要用 docker compose down -v,-v 会连数据一起删。
9.2 单机的重启顺序
整机重启时,docker 会和业务服务同时启动,开机那 1~2 分钟可能 Harbor 还没 healthy,业务容器就去拉镜像会失败。正确顺序:
bash
systemctl restart docker
sleep 30
cd /opt/harbor && docker compose up -d
把这两条写成开机脚本更稳(或给业务容器的启动加 depends/重试)。
9.3 备份(单机没有第二份,备份更重要)
bash
mkdir -p /opt/backup
cp /opt/harbor/harbor.yml /opt/backup/harbor.yml.$(date +%F)
bash
cd /opt/harbor
docker compose stop
tar czf /opt/backup/harbor-data-$(date +%F).tar.gz /data
docker compose start
必须停服再 tar,否则数据库正在写,备份可能是坏的。只备数据库元数据(可不停服,适合每天跑):
bash
docker exec harbor-db pg_dump -U postgres registry \
> /opt/backup/registry-$(date +%F).sql
9.4 清理磁盘(删了镜像空间没变小的原因)
网页删镜像只是打标记,必须跑 GC 才真正释放:
bash
cd /opt/harbor
docker compose stop
bash
# 先演练,看能回收多少
docker run -it --name gc --rm --volumes-from registry \
goharbor/registry-photon:v2.9.0 \
garbage-collect --dry-run /etc/registry/config.yml
bash
# 数字合理后去掉 --dry-run 真执行
docker run -it --name gc --rm --volumes-from registry \
goharbor/registry-photon:v2.9.0 \
garbage-collect /etc/registry/config.yml
bash
docker compose start
更省心:项目 → 策略 → 新建规则 → 保留最近 N 个标签,让 Harbor 自动清。
9.5 日志位置
| 路径 | 用途 |
|---|---|
/var/log/harbor/core.log |
登录、权限、API 报错 |
/var/log/harbor/registry.log |
push / pull 报错 |
/var/log/harbor/nginx/ |
访问日志 |
journalctl -u docker -n 100 |
docker 本身的问题 |
附录 A:一键脚本全文(install-harbor-single.sh)
见同目录文件 install-harbor-single.sh。核心逻辑摘要:
- root 检查、CentOS 7 判断
- 检查 IP/端口/磁盘,端口被占且未显式指定则提示
- 备份并重写 CentOS-Base.repo(vault 归档源)
- 安装 docker-ce 20.10.18(已装则跳过)
- 用 python 合并 daemon.json(不覆盖你已有的其它配置)
- 安装 docker-compose-plugin(失败则下载二进制兜底)
- 下载并解压 harbor-offline-installer-v2.9.0.tgz(已下载则跳过)
- sed 改 harbor.yml 的 hostname / port / password
./prepare && ./install.sh,最后打印访问地址和登录信息
可重跑:已装好的步骤会跳过;/opt/harbor 已存在时会停下来问你要不要覆盖。
附录 B:彻底卸载
只重装 Harbor,保留镜像数据
bash
cd /opt/harbor && docker compose down # 不加 -v
cd /opt && mv harbor harbor.bak
# 重新解压安装包,把备份的 harbor.yml 拷回,./prepare && ./install.sh
全部清空(不可恢复)
bash
cd /opt/harbor && docker compose down -v
rm -rf /data /opt/harbor /var/log/harbor
连 Docker 一起卸
bash
systemctl stop docker
yum remove -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
rm -rf /var/lib/docker /var/lib/containerd /etc/docker