1. 为什么需要自己搭一个 Harbor?——从“Docker Hub 被限速”说起
Harbor 不是 Docker 的替代品,而是 Docker 生态里真正能让你把镜像“管起来”的那把锁。我第一次在客户现场踩坑,就是因为没提前搭 Harbor:开发团队用docker push往 Docker Hub 推一个 800MB 的 Spring Boot 镜像,推到 92% 卡住,重试三次全失败;运维同事查日志发现是 Docker Hub 对未认证账户的匿名推送限速到了 100KB/s,而他们用的是公司统一账号,但账号下绑了 17 个子团队,触发了速率熔断机制。这时候没人再关心“Docker 怎么安装”,所有人盯着屏幕问:“有没有办法让镜像不走公网?能不能本地存、本地验、本地删?”——答案就是 Harbor。
它不是个玩具,而是一套企业级镜像生命周期管理平台。你看到的“上传/下载镜像”,背后其实是四层能力在协同:存储层(底层用的是 Registry v2 协议)+ 权限层(项目级 RBAC,支持 LDAP/AD 同步)+ 安全层(镜像扫描、内容信任签名、漏洞报告)+ 治理层(镜像保留策略、GC 自动清理、审计日志归档)。很多教程只教“docker-compose up -d启起来就行”,结果上线三天就出事:某开发误删了生产环境的nginx:alpine镜像,因为 Harbor 默认项目权限是“开发可读写”,没人配过只读策略;还有一次,安全团队要求所有镜像必须通过 Clair 扫描后才能部署,但 Harbor 里没开扫描器开关,CI 流水线照常发布,最后被红队打穿。
所以这篇不是“Docker Desktop 上点几下就能跑的 Demo 教程”。我会带你从 CentOS 7.9 物理机开始,完整复现一个生产可用、权限可控、日志可溯、扫描可配的 Harbor 实例。重点讲清楚三个别人不说透的细节:
- 为什么 Harbor 必须用 HTTPS?HTTP 模式下
docker login会报x509: certificate signed by unknown authority,这不是证书问题,而是 Docker 客户端强制策略; - 为什么
docker push harbor.example.com/project/app:v1.2里的域名必须和 Harbor 配置的hostname完全一致?少一个字符,push 就返回unauthorized: authentication required; - 为什么用
docker-compose启动后,harbor-core容器总在重启?大概率是/data目录权限不对,或者 PostgreSQL 初始化失败但日志被logrotate切走了。
你不需要会写 Go 或 Python,但得懂 Linux 文件权限、SSL 证书链、Docker daemon.json 配置逻辑。如果你正卡在“镜像上传 401”或“pull 报错 no basic auth credentials”,这篇文章会直接给你定位路径和修复命令。
2. 安装前必须确认的五件事——别跳过检查清单
Harbor 看似一键部署,实则对环境极其挑剔。我见过太多人卡在第 3 步,反复重装三次才发现是 SELinux 没关。下面这五件事,每一条都对应一个真实故障场景,必须逐项验证:
2.1 确认系统版本与内核参数(CentOS 7.9 是黄金组合)
Harbor 官方明确支持CentOS 7.6+ / Ubuntu 18.04+ / RHEL 7.6+,但实际生产中,CentOS 7.9 是最稳的选择。原因有三:
- Docker CE 20.10.x 在 CentOS 7.9 上的 cgroup v1 兼容性最好,而 Harbor 的
registry组件重度依赖 cgroup 内存限制; firewalld规则模板在 7.9 中最全,harbor.yml里配置的80/443/4443端口能自动映射;systemd版本 219 支持RestartSec=10这类精细重启控制,避免harbor-jobservice崩溃后无限重启拖垮宿主机。
提示:执行
cat /etc/redhat-release && uname -r,输出必须是CentOS Linux release 7.9.2009和3.10.0-1160.el7.x86_64。如果内核低于3.10.0-1127,请先yum update -y && reboot,否则 Harbor 启动后registry容器会因memcg参数不识别而退出。
2.2 关闭 SELinux 与 firewalld(不是“建议”,是硬性要求)
Harbor 的clair(漏洞扫描器)和notary(内容信任服务)组件需要访问/data目录下的 socket 文件,而 SELinux 默认禁止跨域 socket 连接。我曾在一个金融客户环境里,SELinux 处于permissive模式,harbor-core日志里疯狂刷avc: denied { connectto } for pid=123 comm="core" path="/data/clair.sock",但容器就是不报错,直到用ausearch -m avc -ts recent才抓到这条拒绝日志。
正确操作只有两种:
- 彻底关闭:
setenforce 0 && sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config - 或者临时放行(仅测试用):
semanage port -a -t http_port_t -p tcp 8080(但 Harbor 默认不用 8080)
firewalld 同理。Harbor 依赖80(HTTP 重定向)、443(HTTPS 主服务)、4443(内部 API)三个端口。firewall-cmd --list-ports输出必须包含80/tcp 443/tcp 4443/tcp,否则docker login会超时。别信“用 iptables 替代”,Harbor 的prepare脚本会主动调用firewall-cmd,iptables 规则会被覆盖。
2.3 磁盘空间与挂载点规划(/data 目录必须独立)
Harbor 默认将所有数据(镜像、数据库、日志、证书)存放在/data目录。很多人直接用df -h看根分区还有 20GB 就开干,结果推入 5 个 Java 镜像后磁盘爆满,harbor-db容器因 PostgreSQL WAL 日志写满而崩溃。
真实需求测算如下:
- 每个 500MB 的应用镜像,实际占用约 750MB(含 layer 压缩、metadata、scan 结果缓存);
- PostgreSQL 数据库每万条镜像记录占 1.2GB;
- 日志默认保留 7 天,每天约 80MB;
- 所以 1TB 磁盘起步,且
/data必须是独立挂载点(如xvdb1),不能是/下的子目录。
验证命令:
# 查看 /data 是否独立挂载 findmnt /data | grep -q "xvdb" && echo "OK" || echo "ERROR: /data not on separate disk" # 检查可用空间(需 > 500GB) df -h /data | awk 'NR==2 {print $4}' | grep -E '^[5-9][0-9]{2}G|[1-9][0-9]{3}G' >/dev/null && echo "Space OK" || echo "Insufficient space"2.4 Docker 版本与 daemon.json 配置(20.10.17 是兼容阈值)
Harbor 2.5+ 要求 Docker Engine ≥ 20.10.10,但实测 20.10.17 最稳。低于此版本,docker push时registry组件会因OCI manifest解析异常返回500 Internal Server Error。
更重要的是daemon.json配置。很多教程忽略这点,导致docker login成功但push失败。关键参数只有两个:
"insecure-registries": ["harbor.example.com"]—— 如果用 HTTP 模式(不推荐),必须加此项;"registry-mirrors": ["https://mirror.gcr.io"]—— 这个镜像源只影响docker pull,不影响 Harbor 上传,但能加速prepare脚本拉取基础镜像。
验证方法:
# 检查 Docker 版本 docker version --format '{{.Server.Version}}' | grep -E '^20\.10\.(1[7-9]|[2-9][0-9])$' >/dev/null && echo "Docker OK" || echo "Upgrade Docker" # 检查 daemon.json 是否存在且语法正确 jq empty /etc/docker/daemon.json 2>/dev/null && echo "daemon.json valid" || echo "daemon.json invalid"2.5 DNS 与 hostname 解析(harbor.example.com 必须能 ping 通)
这是最隐蔽的坑。Harbor 安装脚本./install.sh会读取harbor.yml中的hostname字段,并生成对应证书。如果hostname设为harbor.example.com,但宿主机/etc/hosts里没配127.0.0.1 harbor.example.com,那么docker login harbor.example.com时,客户端会尝试解析 DNS,而内网 DNS 通常没有这条记录,最终超时。
正确做法分两步:
- 修改
/etc/hosts:echo "127.0.0.1 harbor.example.com" >> /etc/hosts - 在
harbor.yml中设置hostname: harbor.example.com(注意:不能写localhost或127.0.0.1,Docker 客户端会拒绝连接)
验证命令:
# 必须返回 0 ping -c1 harbor.example.com &>/dev/null && echo "DNS OK" || echo "Fix /etc/hosts" # 检查 hostname 是否匹配 grep "^hostname:" harbor.yml | grep -q "harbor.example.com" && echo "Config OK" || echo "Edit harbor.yml"3. 安装过程详解:从下载到启动的每一步意图
Harbor 安装本质是三阶段流水线:准备环境 → 生成配置 → 启动容器。网上很多教程把./install.sh当黑盒,但一旦失败,你连日志都找不到。下面我拆解每个命令背后的意图,告诉你该看什么日志、该改什么参数。
3.1 下载离线安装包并校验(为什么必须用 offline 包?)
Harbor 官方提供 online 和 offline 两种安装包。online 包只有 1.2MB,启动时动态拉取 1.2GB 的镜像;offline 包 1.2GB,自带全部镜像。生产环境必须用 offline 包,理由有二:
- 网络不可靠:
prepare脚本拉取goharbor/harbor-core:v2.5.3时若中断,不会重试,直接报错failed to pull image; - 安全合规:金融客户要求所有镜像必须经内部 Nexus 代理,online 包的
docker-compose.yml里写死image: goharbor/xxx,无法替换 registry 地址。
下载命令:
# 从官网下载(注意版本号) wget https://github.com/goharbor/harbor/releases/download/v2.5.3/harbor-offline-installer-v2.5.3.tgz # 校验 SHA256(官网页面有 checksum) echo "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 harbor-offline-installer-v2.5.3.tgz" | sha256sum -c解压后目录结构必须包含:
harbor.yml.tmpl(模板文件)install.sh(主安装脚本)common.sh(环境检测函数)prepare(核心配置生成器)
注意:不要
mv harbor /opt/harbor,必须保持解压后目录名为harbor。install.sh里硬编码了./prepare路径,改名会导致command not found。
3.2 配置 harbor.yml:七个必改参数与三个隐藏陷阱
harbor.yml是 Harbor 的心脏。官方模板有 200 行,但生产环境只需改 7 个参数。其余参数要么有默认值,要么留空即可。下面列出必须修改项,并解释每个值的业务含义:
| 参数 | 示例值 | 为什么必须改 | 关联风险 |
|---|---|---|---|
hostname | harbor.example.com | Docker 客户端认证域名,必须和docker login命令一致 | 改成harbor.local会导致x509: certificate is valid for harbor.local, not harbor.example.com |
http.port | 80 | HTTP 端口,用于重定向到 HTTPS | 设为8080时,docker login必须带端口harbor.example.com:8080,但 Docker 默认不支持带端口的 registry |
https.port | 443 | HTTPS 主端口,所有 API 和 Web 访问走这里 | 若设为8443,浏览器访问https://harbor.example.com:8443可打开,但docker login仍会连443 |
data_volume | /data | 数据根目录,所有子目录(database、redis、jobservice)都在此下 | 若指向/home/harbor/data,chown -R 10000:10000 /home/harbor/data会失败,因为/home分区通常是noexec |
database.password | Harbor12345 | PostgreSQL 密码,长度必须 ≥ 8 且含大小写字母+数字 | 密码含@符号会导致prepare脚本解析 YAML 失败,报yaml: line X: did not find expected key |
harbor_admin_password | Harbor12345 | Web 管理后台密码,同上规则 | 密码太简单,登录后会被强制跳转到密码修改页,API 调用返回401 Unauthorized |
clair.updater_interval | 12 | Clarity 漏洞库更新间隔(小时),设为0表示禁用 | 设为1会导致每小时拉取 2GB 的 NVD 数据库,拖慢整个 Harbor 响应 |
三个隐藏陷阱:
- 陷阱一:
certificate和private_key路径必须是绝对路径。模板里写./cert/harbor.crt,但prepare脚本会把它拼成/root/harbor/cert/harbor.crt,而证书实际在/data/cert/。正确写法是/data/cert/harbor.crt。 - 陷阱二:
trivy.ignore_unfixed设为true时,扫描结果里不显示未修复漏洞。金融客户要求必须显示所有 CVE,所以必须设为false。 - 陷阱三:
notification.endpoint如果配了企业微信 webhook,URL 必须以https://开头。配http://会导致harbor-core容器启动失败,日志报invalid URL scheme。
3.3 执行 prepare 脚本:生成配置文件的底层逻辑
./prepare不是魔法,它只是个 Python 脚本(./common/prepare),作用是把harbor.yml里的参数注入到 12 个模板文件中。最关键的三个输出文件是:
common/config/registry/config.yml:Registry 服务配置,定义 storage backend(默认 filesystem)、auth(指向 core 服务)、middleware(blob cache);common/config/core/app.conf:Core 服务配置,含数据库连接串、Redis 地址、Clair 扫描器地址;docker-compose.yml:最终的容器编排文件,定义harbor-core、harbor-registry、harbor-db等 8 个服务。
执行时加-f参数可指定 yml 文件:
./prepare -f harbor.yml成功标志:
- 控制台输出
Generated configuration file: ./common/config/registry/config.yml等 12 行; ls -l common/config/下所有文件时间戳都是当前时间;docker-compose.yml里services.harbor-core.environment.HARBOR_ADMIN_PASSWORD的值是加密后的字符串(不是明文)。
失败常见原因:
harbor.yml语法错误:yaml.parser.ParserError: while parsing a block mapping,用python -c "import yaml; print(yaml.load(open('harbor.yml'), Loader=yaml.FullLoader))"检查;/data目录不存在或无写权限:OSError: [Errno 13] Permission denied: '/data/secret',执行mkdir -p /data && chown 10000:10000 /data;- 证书文件路径错误:
IOError: [Errno 2] No such file or directory: '/data/cert/harbor.crt',确认证书已放入/data/cert/且权限为644。
3.4 启动 Harbor:docker-compose up -d 的真相
docker-compose up -d启动的是docker-compose.yml定义的 8 个服务。但它们的启动顺序有强依赖:
harbor-db(PostgreSQL)→ 2.redis→ 3.harbor-registry→ 4.harbor-core→ 5.harbor-jobservice→ 6.harbor-portal→ 7.harbor-trivy→ 8.nginx
docker-compose本身不保证顺序,Harbor 用healthcheck解决:每个服务的docker-compose.yml里都有healthcheck.test字段,例如harbor-core的健康检查是curl -f http://localhost:8080/api/v2.0/ping || exit 1。只有前一个服务健康,后一个才启动。
启动后验证命令:
# 查看所有容器状态(必须全是 Up) docker-compose ps | awk 'NR>1 {print $1,$4}' | grep -v "Up" # 检查 harbor-core 是否监听 8080 docker exec harbor-core ss -tlnp | grep ":8080" # 检查 nginx 是否转发 443 到 8080 docker exec nginx ss -tlnp | grep ":443"如果harbor-core一直 restarting,看日志:
docker logs harbor-core 2>&1 | tail -20高频错误:
failed to connect to database: dial tcp 172.19.0.2:5432: connect: connection refused→harbor-db没起来,查docker logs harbor-db;failed to initialize database: pq: password authentication failed for user "harbor"→harbor.yml里database.password和harbor_db_password不一致;failed to create table: pq: relation "project" does not exist→ PostgreSQL 数据库初始化失败,删掉/data/database重来。
4. Docker 上传与下载镜像:从 login 到 push 的全流程实操
安装完成只是起点。真正的价值在于让开发能顺畅地push和pull。这一节我用一个真实 Spring Boot 应用为例,从代码打包到镜像上传,全程演示,并标注每个命令背后的网络请求和权限校验点。
4.1 准备测试镜像:构建一个带标签的 Spring Boot 镜像
假设你有一个 Maven 项目,pom.xml里已配好spring-boot-maven-plugin。构建命令:
# 打包成 fat jar mvn clean package -DskipTests # 构建 Docker 镜像(注意:tag 必须含 Harbor 域名) docker build -t harbor.example.com/library/springboot-demo:v1.0 . # Dockerfile 内容(关键点:FROM 必须用 public 镜像,不能用 private registry) FROM openjdk:11-jre-slim VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]提示:
docker build时-t参数的格式必须是harbor.example.com/项目名/镜像名:标签。harbor.example.com是 registry 地址,library是 Harbor 里的项目名(需提前在 Web 界面创建),springboot-demo是镜像名,v1.0是标签。少任何一部分,push都会失败。
4.2 Docker login:认证流程的三次握手
docker login不是简单发个 token,而是标准的 Docker Registry V2 认证协议:
- 客户端向
https://harbor.example.com/service/token?account=admin&service=harbor-registry发 GET 请求; - Harbor 返回 JWT token 和 realm(认证域);
- 客户端用该 token 作为 Bearer Auth,向
https://harbor.example.com/v2/发 HEAD 请求验证权限。
执行命令:
docker login harbor.example.com -u admin -p Harbor12345成功标志:
- 输出
Login Succeeded; ~/.docker/config.json里新增"harbor.example.com": {"auth": "YWRtaW46SGFyYm9yMTIzNDU="}(base64 编码的admin:Harbor12345);docker info | grep -A5 "Insecure Registries"显示harbor.example.com(如果用了 HTTP 模式)。
失败排查:
Error response from daemon: Get https://harbor.example.com/v2/: x509: certificate signed by unknown authority→ 客户端没信任 Harbor 的 CA 证书。解决:把/data/cert/harbor.crt复制到/etc/docker/certs.d/harbor.example.com/ca.crt,然后systemctl restart docker;Error response from daemon: Get https://harbor.example.com/v2/: unauthorized: authentication required→ 用户密码错,或项目library的成员里没加admin用户(Web 界面检查)。
4.3 Push 镜像:四步校验与失败定位
docker push是最易出错的环节。它实际执行四步校验:
- Registry 可达性校验:
curl -I https://harbor.example.com/v2/,返回200 OK; - 项目存在性校验:
curl -X GET https://harbor.example.com/api/v2.0/projects/library,返回200; - 用户权限校验:
curl -X GET https://harbor.example.com/api/v2.0/projects/library/members,检查admin是否在role_id: 1(项目管理员)列表中; - Layer 上传校验:对每个 layer 发
POST /v2/library/springboot-demo/blobs/uploads/,返回202 Accepted。
执行命令:
docker push harbor.example.com/library/springboot-demo:v1.0进度条显示The push refers to repository [harbor.example.com/library/springboot-demo]后,会依次上传layer、config、manifest。如果卡在某个 layer,看日志:
# 在 Harbor 宿主机上实时监控 registry 日志 docker logs -f harbor-registry 2>&1 | grep -E "(PUSH|Blob|manifest)"高频错误:
denied: requested access to the resource is denied→ 用户没library项目的Developer或更高权限;unauthorized: authentication required→docker login的 token 过期(默认 24 小时),重新docker login;received unexpected HTTP status: 500 Internal Server Error→harbor-registry存储层失败,检查/data/registry目录权限是否为10000:10000。
4.4 Pull 镜像:从 Harbor 下载到本地的完整链路
docker pull比 push 简单,但仍有陷阱。命令:
# 先登出其他 registry,避免 credential 冲突 docker logout # 登录 Harbor docker login harbor.example.com -u admin -p Harbor12345 # 拉取镜像 docker pull harbor.example.com/library/springboot-demo:v1.0拉取过程分三步:
- 获取 manifest:
GET /v2/library/springboot-demo/manifests/v1.0,返回 JSON 描述所有 layer; - 下载 config:
GET /v2/library/springboot-demo/blobs/sha256:xxx; - 下载 layers:并发
GET /v2/library/springboot-demo/blobs/sha256:yyy。
验证是否成功:
# 查看本地镜像 docker images | grep springboot-demo # 运行测试 docker run -d -p 8080:8080 harbor.example.com/library/springboot-demo:v1.0 curl http://localhost:8080/actuator/health注意:
docker pull时,如果本地已有同名镜像(如springboot-demo:v1.0),它不会覆盖,而是创建新镜像 ID。要确保用harbor.example.com/前缀,否则会从 Docker Hub 拉取。
5. 常见问题与排查技巧实录:那些文档里不会写的坑
Harbor 的报错信息向来以“优雅的模糊”著称。下面是我三年运维中整理的 7 个高频问题,每个都附带 root cause、日志定位命令和一行修复命令。
5.1 问题:harbor-core 容器反复重启,日志显示failed to connect to redis
现象:docker-compose ps显示harbor-core状态为Restarting,docker logs harbor-core最后一行是failed to connect to redis: dial tcp 172.19.0.3:6379: connect: connection refused。
Root Cause:harbor-redis容器启动失败,但docker-compose ps里状态还是Up(因为 healthcheck 没配)。真实原因是 Redis 配置内存不足,/data/redis目录权限不对,或redis.conf里maxmemory设太高。
日志定位:
# 查看 redis 容器日志 docker logs harbor-redis # 如果输出为空,说明容器根本没启动 docker inspect harbor-redis | grep -A5 "Status"修复命令:
# 删除旧数据(谨慎!) rm -rf /data/redis # 重启 redis docker-compose restart harbor-redis # 等 30 秒后检查 docker exec harbor-redis redis-cli ping # 应返回 PONG5.2 问题:Web 界面能打开,但登录后 404,或点击“Projects”空白
现象:浏览器访问https://harbor.example.com显示登录页,输入 admin 密码后跳转到https://harbor.example.com/harbor/projects,页面空白,F12 看 Network 标签页,/api/v2.0/projects返回401 Unauthorized。
Root Cause:harbor-core服务的 JWT token 签名密钥不一致。harbor.yml里secretkey_path默认是/data,但prepare脚本生成的密钥文件在/data/secret/下,而容器里挂载的是/data,导致core读不到密钥。
日志定位:
# 查看 core 日志中的 token 错误 docker logs harbor-core 2>&1 | grep -i "token" | tail -5修复命令:
# 重建 secret 目录 mkdir -p /data/secret chown 10000:10000 /data/secret # 重新运行 prepare ./prepare -f harbor.yml # 重启 docker-compose down && docker-compose up -d5.3 问题:docker push 时卡在 “waiting for layer upload”,数小时不动
现象:docker push harbor.example.com/library/app:v1后,进度条停在waiting for layer upload,docker logs harbor-registry无新日志。
Root Cause:Nginx 反向代理超时。Harbor 的nginx.conf里proxy_read_timeout默认 600 秒(10 分钟),但大镜像上传可能超时。Nginx 先断开连接,registry还在等数据,造成僵死。
日志定位:
# 查看 nginx 错误日志 docker logs harbor-nginx 2>&1 | grep -i "timeout"修复命令:
# 修改 nginx 配置(在宿主机上) sed -i 's/proxy_read_timeout 600;/proxy_read_timeout 3600;/' /compose_location/nginx.conf # 重启 nginx docker-compose restart harbor-nginx5.4 问题:Clair 扫描器一直显示 “Pending”,never start
现象:Web 界面点击镜像的 “Scan” 按钮,状态变成 “Pending”,10 分钟后还是 Pending,docker logs harbor-trivy无输出。
Root Cause:Trivy 数据库初始化失败。harbor.yml里trivy.skip_update设为true时,Trivy 不会下载漏洞库,但harbor-core仍会发扫描请求,导致 pending。
日志定位:
# 查看 trivy 日志 docker logs harbor-trivy # 如果输出 “DB schema migration failed”,说明数据库损坏修复命令:
# 清空 trivy 数据 rm -rf /data/trivy # 设置 skip_update 为 false(首次启动必须下载) sed -i 's/skip_update: true/skip_update: false/' harbor.yml # 重新 prepare ./prepare -f harbor.yml # 重启 docker-compose restart harbor-trivy5.5 问题:上传镜像后,Web 界面看不到,但 docker pull 能成功
现象:docker push返回Pushed,docker pull成功,但 Harbor Web 界面的library项目里没有这个镜像。
Root Cause:项目设置了“机器人账户”(Robot Account),但docker login用的是admin用户,而admin不在该项目的成员列表中。Harbor 的权限模型是:项目级权限 > 用户全局权限。
日志定位:
# 查看 core 日志中的权限拒绝 docker logs harbor-core 2>&1 | grep -i "permission denied" | tail -3修复命令:
# 在 Web 界面操作:Projects → library → Members → + Add Member → User: admin → Role: Project Admin # 或用 API(需 token) curl -X POST "https://harbor.example.com/api/v2.0/projects/1/members" \ -H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)" \ -H "Content-Type: application/json" \ -d '{"member_user":{"username":"admin"},"role_id":1}'5.6 问题:harbor-db 容器启动失败,日志显示 “FATAL: could not open relation mapping file”
现象:docker-compose ps显示harbor-db状态为Exit 1,docker logs harbor-db输出FATAL: could not open relation mapping file "global/pg_filenode.map"。
Root Cause:PostgreSQL 数据目录损坏。常见于强制docker kill或宿主机断电后,WAL 日志未刷盘。
日志定位:
# 查看详细错误 docker logs harbor-db 2>&1 | head -20修复命令:
# 备份旧数据(重要!) cp -r /data/database /data/database.backup # 初始化新数据库 docker run -it --rm -v /data/database:/var/lib/postgresql/data gosu postgres postgres --initdb # 重启 docker-compose restart harbor-db5.7 问题:docker login 报错 “Error saving credentials: error storing credentials”
现象:docker login harbor.example.com