企业研发体系中,制品通常不只有 Maven 的 JAR,还包括 npm、pnpm、Yarn 使用的 JavaScript/TypeScript 包,以及 Docker、Podman、containerd 和 Kubernetes 使用的 OCI 镜像。
如果所有构建节点都直接访问公网 npm Registry 和 Docker Hub,会逐渐暴露出以下问题:
公网速度和稳定性不可控;
大量机器重复下载同一份依赖和镜像;
企业内部包和镜像缺少统一权限管理;
CI/CD 凭证分散,难以轮换;
公共包被删除、篡改或版本漂移后难以复现构建;
Docker Hub Pull Rate Limit 影响流水线;
镜像缺少漏洞扫描、签名、保留和清理策略;
内网、弱网和离线环境无法稳定构建;
私有包命名不规范时可能产生 Dependency Confusion;
开发、测试和生产镜像缺少晋级与不可变策略。
本文在 Rocky Linux 10 上搭建两套企业制品服务:
1 2 3 4 5 6 Verdaccio: 提供 npm Registry、私有 npm 包发布和公共依赖代理缓存 Harbor: 提供 Docker/OCI 镜像仓库、项目级 RBAC、Robot Account、 Trivy 漏洞扫描、复制、Proxy Cache、Tag Retention 和 Immutable Tag
另外补充 CNCF Distribution Registry 3.1.1 的轻量部署方案,适合 HomeLab、临时流水线或只需要基础 Push/Pull 的环境。
本文编写时的版本基线为 Verdaccio 6.9.0、Harbor 2.15.2、Distribution Registry 3.1.1。生产部署前应重新查看 Release Notes,并固定到经过测试的 Patch 版本或镜像 Digest。
一、整体架构 本文使用以下示例:
服务
域名
内部端口
数据目录
Verdaccio
npm.example.com
4873
/data/verdaccio
Harbor
harbor.example.com
443
/data/harbor
Distribution Registry(可选)
registry.example.com
5000
/data/registry
flowchart TD
Dev[开发者] --> NPM[npm.example.com]
CI[CI/CD] --> NPM
Dev --> Harbor[harbor.example.com]
CI --> Harbor
K8S[Kubernetes / Docker Hosts] --> Harbor
NPM --> Verdaccio[Verdaccio]
Verdaccio --> NPMJS[registry.npmjs.org]
Verdaccio --> NPMStorage[(npm Storage)]
Harbor --> Core[Harbor Core]
Harbor --> Registry[OCI Distribution]
Harbor --> Trivy[Trivy Scanner]
Harbor --> HarborDB[(PostgreSQL)]
Harbor --> HarborRedis[(Redis)]
Registry --> ImageStorage[(Image Storage)]
1.1 是否可以部署在同一台服务器 可以,但生产环境需要评估:
npm 包和容器镜像是否争抢同一块数据盘;
Harbor Trivy 扫描会占用 CPU、内存和磁盘;
Harbor 垃圾回收与 Verdaccio 备份是否同时执行;
故障是否会导致所有制品服务一起不可用;
容量增长是否可预测;
是否需要不同的备份和恢复等级。
推荐:
1 2 3 4 5 6 7 小型团队 / HomeLab: 一台 Rocky Linux 10 服务器部署 Verdaccio + Harbor 中大型生产: Verdaccio 与 Harbor 分开部署 Harbor 数据盘单独规划 制品备份存储与运行存储分离
二、为什么 npm 使用 Verdaccio、Docker 使用 Harbor 2.1 Verdaccio Verdaccio 的特点:
轻量;
npm、pnpm、Yarn 兼容;
可代理 npmjs;
自动缓存公共依赖;
支持私有包;
默认文件系统存储;
支持 htpasswd 和插件化认证;
适合中小团队与内部组件库。
Verdaccio 不适合直接被当作大规模多活分布式 npm 平台。默认文件系统存储也不适合多个实例同时写同一个普通共享目录。
2.2 Harbor Harbor 在 OCI Distribution 基础上增加:
Web 管理界面;
用户、项目和角色;
Robot Account;
漏洞扫描;
Tag Retention;
Tag Immutability;
Proxy Cache;
跨 Registry 复制;
LDAP/AD/OIDC;
审计日志;
Cosign、Notation 等签名关联;
OCI Artifact 与 Helm Chart。
Harbor 更适合作为生产 Docker/OCI 私服。
2.3 Distribution Registry Distribution Registry 适合:
单机基础 Push/Pull;
临时环境;
HomeLab;
离线传输节点;
只需要 Basic Auth 和 TLS;
不需要 Web UI、RBAC、扫描和复制。
三、资源规划 3.1 Verdaccio
规模
CPU
内存
数据盘
测试
1 核
1 GB
20 GB SSD
小型团队
2 核
2~4 GB
100 GB SSD
中型团队
4 核
4~8 GB
300 GB 以上 SSD
3.2 Harbor Harbor 单机 Compose 部署建议:
项目
最低
推荐
CPU
2 核
4 核以上
内存
4 GB
8 GB 以上
磁盘
40 GB
160 GB 以上
启用 Trivy、Proxy Cache、大量多架构镜像和 Build Cache 时,建议 8 核、16 GB 内存和独立 SSD/NVMe 数据盘。
3.3 容量估算 容器镜像 Layer 可以共享,因此实际容量取决于基础镜像复用率、架构数量、Tag、缓存、扫描数据库和垃圾回收策略。
建议初始容量:
持续监控:
1 2 3 4 df -hT /datadf -ih /datadu -sh /data/verdacciodu -sh /data/harbor
四、Rocky Linux 10 系统初始化 1 2 3 4 cat /etc/rocky-releaseuname -runame -m hostnamectl
设置主机名和时间:
1 2 3 4 5 hostnamectl set-hostname registry01.example.com timedatectl set-timezone Asia/Shanghai systemctl enable --now chronyd chronyc tracking
更新系统:
1 2 3 dnf clean all dnf makecache dnf upgrade -y
安装工具:
1 2 3 dnf install -y \ vim curl wget jq tar gzip unzip rsync openssl \ httpd-tools bind-utils lsof policycoreutils-python-utils firewalld
保持 SELinux:
启动防火墙:
1 systemctl enable --now firewalld
五、安装 Docker Engine 卸载冲突包:
1 2 3 4 dnf remove -y \ docker docker-client docker-client-latest docker-common \ docker-latest docker-latest-logrotate docker-logrotate \ docker-engine podman-docker
添加仓库并安装:
1 2 3 4 5 6 7 8 9 dnf install -y dnf-plugins-core dnf config-manager \ --add-repo \ https://download.docker.com/linux/rhel/docker-ce.repo dnf install -y \ docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin
启动:
1 2 3 4 5 systemctl enable --now docker docker version docker compose version docker info
不要开放未认证的 tcp://0.0.0.0:2375。
5.1 Docker 日志轮转 创建或合并 /etc/docker/daemon.json:
1 2 3 4 5 6 7 { "log-driver" : "local" , "log-opts" : { "max-size" : "50m" , "max-file" : "5" } }
1 2 jq . /etc/docker/daemon.json systemctl restart docker
六、DNS 与域名 企业 DNS:
1 2 npm.example.com -> 192.168.9.10 harbor.example.com -> 192.168.9.10
测试环境可临时配置:
1 2 3 4 cat >> /etc/hosts <<'EOF2' 192.168.9.10 npm.example.com 192.168.9.10 harbor.example.com EOF2
1 2 getent hosts npm.example.com getent hosts harbor.example.com
第一部分:搭建 Verdaccio npm 私服 七、Verdaccio 目录规划 本文使用 Verdaccio 6.9.0。
1 2 mkdir -p /opt/verdacciomkdir -p /data/verdaccio/{conf,storage,plugins,logs}
官方容器使用 UID 10001、GID 65533:
1 2 3 chown -R 10001:65533 /data/verdacciochmod 0750 /data/verdacciochmod 0750 /data/verdaccio/{conf,storage,plugins}
八、配置 Verdaccio 创建 /data/verdaccio/conf/config.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 storage: /verdaccio/storage plugins: /verdaccio/plugins auth: htpasswd: file: /verdaccio/conf/htpasswd max_users: -1 algorithm: bcrypt rounds: 12 uplinks: npmjs: url: https://registry.npmjs.org/ timeout: 30s packages: '@company/*': access: $authenticated publish: $authenticated unpublish: npm-admin '@*/*': access: $authenticated publish: npm-admin unpublish: npm-admin proxy: npmjs '**': access: $authenticated publish: npm-admin unpublish: npm-admin proxy: npmjs middlewares: audit: enabled: true security: api: jwt: sign: expiresIn: 15d notBefore: 0 web: sign: expiresIn: 1h notBefore: 0 server: keepAliveTimeout: 60 publish: check_owners: true keep_readmes: tagged web: enable: true title: Company npm Registry scope: '@company' login: true showInfo: true showSettings: true showFooter: true userRateLimit: windowMs: 900000 max: 100 log: type: stdout format: json level: http
权限:
1 2 chown 10001:65533 /data/verdaccio/conf/config.yamlchmod 0640 /data/verdaccio/conf/config.yaml
8.1 Dependency Confusion 防护 企业私有包统一使用 @company/*,并且私有 Scope 不配置 proxy: npmjs。这样本地不存在 @company/payment-sdk 时,Verdaccio 不会去公网寻找同名包。
8.2 禁止自助注册
用户由管理员创建,以便审批、回收和审计。
九、创建 Verdaccio 用户 1 2 3 4 5 6 7 8 9 10 11 htpasswd -B -C 12 -c \ /data/verdaccio/conf/htpasswd \ npm-admin htpasswd -B -C 12 \ /data/verdaccio/conf/htpasswd \ alice htpasswd -B -C 12 \ /data/verdaccio/conf/htpasswd \ ci-publisher
1 2 3 chown 10001:65533 /data/verdaccio/conf/htpasswdchmod 0640 /data/verdaccio/conf/htpasswdcut -d: -f1 /data/verdaccio/conf/htpasswd
删除用户:
1 htpasswd -D /data/verdaccio/conf/htpasswd alice
删除用户不一定会立即撤销已签发 Token,应结合 Token 有效期和 CI Secret 回收。
十、Verdaccio Docker Compose 创建 /opt/verdaccio/compose.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 services: verdaccio: image: verdaccio/verdaccio:6.9.0 container_name: verdaccio restart: unless-stopped environment: VERDACCIO_PORT: "4873" NODE_OPTIONS: "--max-old-space-size=1536" ports: - "127.0.0.1:4873:4873" volumes: - /data/verdaccio/conf:/verdaccio/conf:Z - /data/verdaccio/storage:/verdaccio/storage:Z - /data/verdaccio/plugins:/verdaccio/plugins:Z healthcheck: test: - CMD-SHELL - wget -qO- http://127.0.0.1:4873/-/ping | grep -q '{}' interval: 30s timeout: 5s retries: 5 start_period: 20s security_opt: - no -new-privileges:true ulimits: nofile: soft: 65536 hard: 65536 mem_limit: 2g cpus: 2.0
启动:
1 2 3 4 5 6 7 8 cd /opt/verdaccio docker compose config docker compose pull docker compose up -d docker compose ps docker compose logs -f verdaccio
验证:
1 2 curl -fsS http://127.0.0.1:4873/-/ping curl -I http://127.0.0.1:4873/
十一、为 Verdaccio 配置 NGINX HTTPS 1 2 dnf install -y nginx systemctl enable --now nginx
证书路径:
1 2 /etc/nginx/ssl/npm.example.com.crt /etc/nginx/ssl/npm.example.com.key
创建 /etc/nginx/conf.d/npm.example.com.conf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 limit_req_zone $binary_remote_addr zone=verdaccio_login:10m rate=2r/s;upstream verdaccio_backend { server 127.0.0.1:4873 ; keepalive 32 ; }server { listen 80 ; server_name npm.example.com; return 301 https://$host $request_uri ; }server { listen 443 ssl; http2 on ; server_name npm.example.com; ssl_certificate /etc/nginx/ssl/npm.example.com.crt; ssl_certificate_key /etc/nginx/ssl/npm.example.com.key; ssl_protocols TLSv1.2 TLSv1.3 ; ssl_session_cache shared:NPM_SSL:20m ; ssl_session_timeout 1d ; client_max_body_size 100m ; access_log /var/log/nginx/npm-access.log; error_log /var/log/nginx/npm-error .log warn ; location = /healthz { access_log off ; proxy_pass http://verdaccio_backend/-/ping; proxy_set_header Host $host ; } location ~ ^/(-/user|-/v1/login|-/npm/v1/user) { limit_req zone=verdaccio_login burst=10 nodelay; proxy_pass http://verdaccio_backend; proxy_http_version 1 .1 ; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Host $host ; proxy_set_header X-Forwarded-Port 443 ; proxy_set_header Connection "" ; } location / { proxy_pass http://verdaccio_backend; proxy_http_version 1 .1 ; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Host $host ; proxy_set_header X-Forwarded-Port 443 ; proxy_set_header Connection "" ; proxy_connect_timeout 5s ; proxy_send_timeout 300s ; proxy_read_timeout 300s ; } }
允许 NGINX 连接后端:
1 2 3 setsebool -P httpd_can_network_connect 1 nginx -t systemctl reload nginx
防火墙:
1 2 3 firewall-cmd --permanent --add-service=http firewall-cmd --permanent --add-service=https firewall-cmd --reload
验证:
1 2 curl -fsS https://npm.example.com/-/ping curl -I https://npm.example.com/
十二、配置 npm、pnpm 和 Yarn 客户端 12.1 只让企业 Scope 走私服 1 2 3 npm config set \ @company:registry \ https://npm.example.com/
项目 .npmrc:
1 @company:registry =https://npm.example.com/
12.2 所有包都走 Verdaccio 1 npm config set registry https://npm.example.com/
1 registry =https://npm.example.com/
12.3 登录 1 2 3 4 5 6 npm login \ --registry=https://npm.example.com/ \ --auth-type=legacy npm whoami \ --registry=https://npm.example.com/
不要将包含 _authToken 的用户级 .npmrc 提交到 Git。
12.4 CI/CD 项目 .npmrc:
1 2 3 @company:registry =https://npm.example.com/ //npm.example.com/:_authToken =${NPM_TOKEN} always-auth =true
CI 平台保存 NPM_TOKEN,不要写入 Dockerfile、Git、构建日志或镜像层。
BuildKit Secret 示例:
1 2 3 4 5 6 7 FROM node:24 -alpine AS buildWORKDIR /app COPY package.json package-lock.json ./ RUN --mount=type =secret,id =npmrc,target=/root/.npmrc npm ci COPY . . RUN npm run build
1 2 3 4 docker buildx build \ --secret id =npmrc,src="$HOME /.npmrc" \ -t example-app:1.0.0 \ .
12.5 pnpm pnpm 直接读取 .npmrc:
12.6 Yarn Berry .yarnrc.yml:
1 2 3 4 5 npmScopes: company: npmRegistryServer: "https://npm.example.com" npmAlwaysAuth: true npmAuthToken: "${NPM_TOKEN}"
十三、发布企业 npm 包 1 2 3 mkdir company-utilscd company-utils npm init --scope=@company
package.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "name" : "@company/utils" , "version" : "1.0.0" , "description" : "Company shared utilities" , "main" : "dist/index.js" , "types" : "dist/index.d.ts" , "files" : [ "dist" , "README.md" ] , "publishConfig" : { "registry" : "https://npm.example.com/" , "access" : "restricted" } , "scripts" : { "build" : "tsc" , "prepublishOnly" : "npm run build && npm test" } }
发布:
查询和安装:
1 2 3 4 npm view @company/utils \ --registry=https://npm.example.com/ npm install @company/utils
预发布版本:
1 2 npm publish --tag beta npm install @company/utils@beta
已经发布的版本尽量保持不可变,修复时发布新版本,而不是覆盖旧版本。
十四、Verdaccio 调优与备份 14.1 存储和 inode 1 2 3 4 df -hT /data/verdacciodf -ih /data/verdacciodu -sh /data/verdaccio/storage find /data/verdaccio/storage -type f | wc -l
推荐 SSD/NVMe、独立数据盘,不建议多个 Verdaccio 实例同时写同一个普通 NFS 目录。
14.2 Uplink Verdaccio 自己已经缓存 npmjs 包,不建议在 NGINX 再无差别缓存 Registry API。重点监控 DNS、TLS、npmjs 响应时间和 429/5xx。
14.3 冷备份 1 2 3 4 5 6 7 8 9 10 11 cd /opt/verdaccio docker compose stop verdacciomkdir -p /backup/verdaccio tar --xattrs --acls \ -czf "/backup/verdaccio/verdaccio-$(date +%F-%H%M%S) .tar.gz" \ -C /data \ verdaccio docker compose start verdaccio
恢复:
1 2 3 4 5 6 7 8 9 10 11 12 cd /opt/verdaccio docker compose downmv /data/verdaccio \ "/data/verdaccio.before-restore-$(date +%F-%H%M%S) " tar -xzf /backup/verdaccio/verdaccio-时间.tar.gz -C /datachown -R 10001:65533 /data/verdaccio restorecon -Rv /data/verdaccio docker compose up -d
验证:
1 2 npm view @company/utils \ --registry=https://npm.example.com/
只备份配置不备份 Storage,等于只保存了仓库门牌,却把货全丢了。
第二部分:搭建 Harbor Docker/OCI 私服 十五、Harbor 安装前规划 本文使用 Harbor 2.15.2,采用官方 Offline Installer 和 Docker Compose 部署。
Harbor 单机安装器包含多个组件:
1 2 3 4 5 6 7 8 9 10 nginx harbor-core harbor-portal harbor-jobservice registry registryctl PostgreSQL Redis Trivy Adapter log
单机 Compose 部署是一个单主机系统 。不要通过 docker compose scale 粗暴扩容 Harbor 组件,否则数据库、Registry、Job Service 和共享存储会变成一场状态管理事故。
需要真正高可用时,推荐使用 Harbor Helm Chart 部署到 Kubernetes,并使用:
外部 PostgreSQL;
外部 Redis;
S3、对象存储或共享存储;
多副本 Core、Portal、Jobservice、Registry;
外部负载均衡;
完整监控和备份。
十六、创建 Harbor 数据盘 1 2 3 4 mkdir -p /opt/harbormkdir -p /data/harbormkdir -p /data/certmkdir -p /backup/harbor
推荐将 /data/harbor 放在独立 SSD、NVMe、RAID 或可靠云盘上。
检查:
1 2 3 4 lsblk -f findmnt /datadf -hT /datadf -ih /data
Harbor Registry 数据不应放在系统根分区。根分区被镜像写满以后,Harbor 会停止工作,Docker 也可能停止工作,顺便让日志系统一起参加故障。
十七、下载并验证 Harbor 安装包 定义版本:
1 2 3 export HARBOR_VERSION='2.15.2' export HARBOR_TAG="v${HARBOR_VERSION} " export HARBOR_ARCHIVE="harbor-offline-installer-${HARBOR_TAG} .tgz"
下载:
1 2 3 4 5 6 7 cd /opt curl -fLO \ "https://github.com/goharbor/harbor/releases/download/${HARBOR_TAG} /${HARBOR_ARCHIVE} " curl -fLO \ "https://github.com/goharbor/harbor/releases/download/${HARBOR_TAG} /${HARBOR_ARCHIVE} .sigstore.json"
从 Harbor 2.15.0 开始,Release Artifact 使用 Cosign 签名。安装 Cosign 2.x 后验证:
1 2 3 4 5 6 7 cosign verify-blob \ --bundle "${HARBOR_ARCHIVE} .sigstore.json" \ --certificate-oidc-issuer \ https://token.actions.githubusercontent.com \ --certificate-identity-regexp \ '^https://github.com/goharbor/harbor/.github/workflows/publish_release.yml@refs/tags/v.*$' \ "${HARBOR_ARCHIVE} "
预期:
验证失败时不要继续安装。重新确认:
下载 URL;
Release Tag;
Bundle 文件;
系统时间;
Cosign 版本;
文件是否被代理或镜像站替换。
解压:
1 2 tar -xzf "${HARBOR_ARCHIVE} " -C /optcd /opt/harbor
检查:
十八、配置 Harbor TLS 证书 生产环境优先使用企业 CA、公开 CA 或 ACME 签发证书。
证书 SAN 必须包含:
18.1 实验环境生成内部 CA 1 2 3 4 5 6 7 8 9 10 11 mkdir -p /root/harbor-pkicd /root/harbor-pki openssl genrsa -out company-root-ca.key 4096chmod 0600 company-root-ca.key openssl req \ -x509 -new -sha512 -days 3650 \ -key company-root-ca.key \ -out company-root-ca.crt \ -subj '/C=CN/O=Example Corporation/OU=Infrastructure/CN=Example Internal Root CA'
生成 Harbor 私钥和 CSR:
1 2 3 4 5 6 7 8 9 10 11 openssl genrsa \ -out harbor.example.com.key \ 3072chmod 0600 harbor.example.com.key openssl req \ -sha512 -new \ -key harbor.example.com.key \ -out harbor.example.com.csr \ -subj '/C=CN/O=Example Corporation/OU=Infrastructure/CN=harbor.example.com'
扩展:
1 2 3 4 5 6 7 cat > harbor.example.com.ext <<'EOF2' authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage=critical,digitalSignature,keyEncipherment extendedKeyUsage=serverAuth subjectAltName=DNS:harbor.example.com,IP:192.168.9.10 EOF2
部分 OpenSSL 版本不接受独立的 digitalSignature= 配置。如果报错,请删除该行,只保留 keyUsage。
签发:
1 2 3 4 5 6 7 8 openssl x509 \ -req -sha512 -days 825 \ -extfile harbor.example.com.ext \ -CA company-root-ca.crt \ -CAkey company-root-ca.key \ -CAcreateserial \ -in harbor.example.com.csr \ -out harbor.example.com.crt
检查:
1 2 3 openssl x509 \ -in harbor.example.com.crt \ -noout -subject -issuer -dates -ext subjectAltName
复制:
1 2 3 4 5 6 7 8 9 10 11 12 13 install -o root -g root -m 0644 \ company-root-ca.crt \ /data/cert/company-root-ca.crt install -o root -g root -m 0644 \ harbor.example.com.crt \ /data/cert/harbor.example.com.crt install -o root -g root -m 0600 \ harbor.example.com.key \ /data/cert/harbor.example.com.key restorecon -Rv /data/cert
十九、配置 harbor.yml 1 2 3 cd /opt/harborcp harbor.yml.tmpl harbor.yml vim harbor.yml
生产基础配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 hostname: harbor.example.com http: port: 80 https: port: 443 certificate: /data/cert/harbor.example.com.crt private_key: /data/cert/harbor.example.com.key harbor_admin_password: "替换为高强度管理员密码" database: password: "替换为高强度数据库密码" max_idle_conns: 100 max_open_conns: 900 conn_max_lifetime: 5m conn_max_idle_time: 0 data_volume: /data/harbor trivy: ignore_unfixed: false skip_update: false skip_java_db_update: false offline_scan: false security_check: vuln insecure: false google_analytics_account: jobservice: max_job_workers: 10 logger_sweeper_duration: 14 notification: webhook_job_max_retry: 3 log: level: info local: rotate_count: 10 rotate_size: 200M location: /var/log/harbor _version: 2.15 .0 proxy: http_proxy: https_proxy: no_proxy: components: - core - jobservice - trivy upload_purging: enabled: true age: 168h interval: 24h dryrun: false cache: enabled: false expire_hours: 24
注意:
hostname 必须与客户端使用的域名一致;
不要把 hostname 写成 https://harbor.example.com;
密码不能使用简单默认值;
data_volume 必须在可靠数据盘;
_version 由模板控制,不要随意改成不存在的 Schema 版本;
代理环境应正确填写 no_proxy,至少排除 Harbor 自身域名和内部容器通信;
harbor.yml 包含明文密码,权限应设置为 0600。
1 chmod 0600 /opt/harbor/harbor.yml
19.1 Trivy 离线环境 完全离线时:
1 2 3 4 trivy: skip_update: true skip_java_db_update: true offline_scan: true
但这并不会凭空产生漏洞数据库。你仍需通过受控流程导入和更新 Trivy DB,否则扫描结果会逐渐过时。
二十、安装 Harbor 先检查 Docker:
1 2 docker version docker compose version
运行安装器并启用 Trivy:
1 2 cd /opt/harbor ./install.sh --with-trivy
查看:
1 2 docker compose ps docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
日志:
1 2 3 4 5 docker compose logs -f core docker compose logs -f registry docker compose logs -f trivy-adapter
验证:
1 2 3 4 5 curl -I https://harbor.example.com/ curl -fsS \ https://harbor.example.com/api/v2.0/health \ | jq
浏览器访问:
1 https://harbor.example.com
初始用户:
密码为 harbor.yml 中的 harbor_admin_password。
首次登录后立即:
修改管理员密码;
创建普通管理员或运维用户;
创建项目;
创建 Robot Account;
配置扫描、保留和不可变规则;
检查邮件、LDAP/OIDC 或审计设置。
二十一、客户端信任 Harbor CA 将 CA 安装到 Rocky Linux 系统信任:
1 2 3 4 5 cp \ /data/cert/company-root-ca.crt \ /etc/pki/ca-trust/source/anchors/company-root-ca.crt update-ca-trust
Docker Daemon 信任目录:
1 2 3 4 5 6 7 8 mkdir -p \ /etc/docker/certs.d/harbor.example.comcp \ /data/cert/company-root-ca.crt \ /etc/docker/certs.d/harbor.example.com/ca.crt systemctl restart docker
如果 Harbor 使用非 443 端口:
1 /etc/docker/certs.d/harbor.example.com:8443/ca.crt
登录:
1 docker login harbor.example.com
不要用:
1 2 3 { "insecure-registries" : [ "harbor.example.com" ] }
去绕过证书问题。应修复 CA、SAN、DNS 和系统时间。
二十二、创建 Harbor 项目和账号 建议项目:
1 2 3 4 5 6 base platform middleware applications sandbox proxy-dockerhub
角色建议:
角色
用途
Project Admin
项目配置、成员、策略
Maintainer
推送、拉取和管理 Artifact
Developer
推送和拉取
Guest
只读并可查看详情
Limited Guest
更受限的只读
Robot Account
CI/CD、节点拉取或跨系统集成
不要让 CI 使用 admin。
22.1 Robot Account 为应用项目创建:
赋予:
1 2 Pull Repository Push Repository
生产节点只拉取时创建:
只授予 Pull。
Robot Secret 只在创建时完整展示,应立即保存到 CI Secret 或 Vault,不要贴到群聊和流水线日志。
二十三、推送和拉取镜像 登录:
1 docker login harbor.example.com
拉取基础镜像:
1 docker pull nginx:1.30.4-alpine
重新打 Tag:
1 2 3 docker tag \ nginx:1.30.4-alpine \ harbor.example.com/base/nginx:1.30.4-alpine
推送:
1 2 docker push \ harbor.example.com/base/nginx:1.30.4-alpine
拉取:
1 2 docker pull \ harbor.example.com/base/nginx:1.30.4-alpine
查看 Digest:
1 2 3 4 docker inspect \ harbor.example.com/base/nginx:1.30.4-alpine \ --format '{{json .RepoDigests}}' \ | jq
生产部署最好使用 Digest:
1 harbor.example.com/base/nginx@sha256:...
Tag 是指针,可以变化;Digest 才是具体内容身份。
二十四、多架构镜像 创建 Builder:
1 2 3 4 5 docker buildx create \ --name company-builder \ --use docker buildx inspect --bootstrap
构建并推送:
1 2 3 4 5 docker buildx build \ --platform linux/amd64,linux/arm64 \ -t harbor.example.com/applications/example-app:1.0.0 \ --push \ .
检查 Manifest:
1 2 docker buildx imagetools inspect \ harbor.example.com/applications/example-app:1.0.0
多架构镜像会增加 Artifact、Manifest 和 Layer 数量,保留与 GC 策略必须考虑所有架构。
二十五、Harbor 漏洞扫描 安装时启用:
1 ./install.sh --with-trivy
Harbor UI 中可以:
手工 Scan;
Push 后自动扫描;
定时扫描全部 Artifact;
查看 CVE、严重级别和修复版本;
阻止存在高风险漏洞的 Artifact 被拉取。
25.1 扫描不是运行时安全 Trivy 静态扫描不能替代:
基础镜像升级;
SBOM;
签名与来源验证;
Runtime Security;
Kubernetes Admission Policy;
Secret 扫描;
Dockerfile 最小化;
容器非 Root;
定期重建镜像。
扫描报告也会因漏洞数据库更新而变化,同一个镜像今天可能是绿色,明天可能突然被新 CVE 点亮。
25.2 调优 Trivy 扫描消耗 CPU、内存、网络和磁盘。建议:
扫描任务错峰;
max_job_workers 不要盲目调大;
大型 Java 镜像预留更多扫描时间;
监控 Trivy DB 更新时间;
避免所有历史镜像每天同时扫描;
高峰构建期间降低扫描并发。
二十六、Tag 不可变和保留策略 26.1 Immutable Tag 建议正式项目设置:
1 release-*、v*、语义版本 Tag 不允许覆盖
例如:
1 2 3 1.0.0 v1.0.0 release-20260801
允许变化的 Tag:
1 2 3 4 dev snapshot nightly latest
即便允许 latest 变化,生产部署仍应使用版本或 Digest。
26.2 Retention Policy 示例:
1 2 3 4 5 保留最近 30 个 dev Tag 保留最近 10 个 nightly Tag 保留最近 20 个非 Release 构建 永久保留所有正式语义版本 保留最近 90 天被拉取过的镜像
先 Dry Run,确认不会删除重要 Artifact,再正式执行。
26.3 Retention 与 Garbage Collection 二者职责不同:
1 2 3 4 5 Retention: 删除不需要的 Manifest、Tag 或 Artifact 引用 Garbage Collection: 回收已经没有任何引用的 Blob 物理空间
正确顺序:
1 2 3 4 Retention -> 等待删除任务完成 -> Garbage Collection Dry Run -> Garbage Collection
只执行 GC 不设置 Retention,绝大多数镜像仍有引用,磁盘不会凭空瘦身。
二十七、Proxy Cache Harbor 可创建 Proxy Cache Project,代理 Docker Hub 或其他兼容 Registry。
示例项目:
拉取 Docker Hub 的 NGINX:
1 2 docker pull \ harbor.example.com/proxy-dockerhub/library/nginx:1.30.4-alpine
第一次:
1 Harbor -> Docker Hub -> 下载并缓存
以后:
注意:
Proxy Cache 项目不能当普通 Push 项目使用;
仍需遵守上游服务条款和限流;
使用上游私有账号时,代理项目必须严格授权;
Harbor 2.15.0 曾有 Proxy Cache 已知问题,本文选择 2.15.2 Patch 版本;
升级前仍应查看当前 Patch 的 Known Issues。
二十八、Harbor Garbage Collection 在 Harbor UI:
1 2 3 Administration -> Clean Up -> Garbage Collection
推荐:
先 Dry Run;
检查预计回收空间;
在低峰期执行;
控制 Worker 数量;
监控 Registry 和 Job Service;
不在大量 Push 时同时执行高强度 GC。
不要手工删除:
1 /data/harbor/registry/docker/registry/v2/blobs
Blob 被多个镜像共享。手工删目录可能让多个镜像同时损坏,破坏方式很高效,恢复方式却一点都不高效。
二十九、Harbor 备份与恢复 Harbor Docker Compose 单机部署最可靠、最容易验证的是冷备份。
备份对象:
1 2 3 4 5 6 /opt/harbor /data/harbor /data/cert Harbor CA 私钥的离线备份 Docker 配置 DNS 和防火墙配置
29.1 冷备份 先在 Harbor 管理界面设置 Read Only,停止新的 Push、删除和策略变更。
停止:
1 2 cd /opt/harbor docker compose down
不要加:
备份:
1 2 3 4 5 6 7 8 9 10 11 mkdir -p /backup/harbor tar --xattrs --acls \ -czf "/backup/harbor/harbor-config-$(date +%F-%H%M%S) .tar.gz" \ /opt/harbor \ /data/cert tar --xattrs --acls \ -czf "/backup/harbor/harbor-data-$(date +%F-%H%M%S) .tar.gz" \ -C /data \ harbor
启动:
1 2 cd /opt/harbor docker compose up -d
验证:
1 2 3 curl -fsS \ https://harbor.example.com/api/v2.0/health \ | jq
退出 Read Only。
29.2 恢复 恢复到相同 Harbor 版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cd /opt/harbor docker compose downmv /data/harbor \ "/data/harbor.before-restore-$(date +%F-%H%M%S) " mkdir -p /data/harbor tar -xzf /backup/harbor/harbor-data-时间.tar.gz -C /data tar -xzf /backup/harbor/harbor-config-时间.tar.gz -C / restorecon -Rv /data/harbor /data/cert /opt/harborcd /opt/harbor ./prepare docker compose up -d
验证:
1 2 3 4 docker login harbor.example.com docker pull \ harbor.example.com/base/nginx:1.30.4-alpine
备份必须实际恢复演练。没恢复过的备份,严格来说只是对未来表达了良好愿望。
三十、Harbor 升级 升级前:
阅读目标版本 Release Notes;
检查升级路径;
备份 /opt/harbor 和 /data/harbor;
保留旧安装器;
在测试环境验证;
进入维护窗口;
设置 Read Only。
大致流程:
1 2 cd /opt/harbor docker compose down
解压新安装器到新目录,将旧 harbor.yml 复制过去,然后迁移配置:
1 2 3 4 5 docker run -it --rm \ -v /:/hostfs \ goharbor/prepare:v2.15.2 \ migrate \ -i /opt/harbor/harbor.yml
再执行:
1 ./install.sh --with-trivy
Harbor 支持升级和升级失败后的回滚流程,但不支持随意 Downgrade。数据库 Schema 一旦升级,不能只换回旧镜像就当一切没发生。
三十一、Harbor 调优 31.1 数据盘 推荐:
SSD/NVMe;
独立逻辑卷;
预留 20%~30% 空间;
监控 inode;
不使用延迟和一致性未知的网络挂载;
备份盘与运行盘分离。
1 2 3 4 df -hT /data/harbordf -ih /data/harbor iostat -xz 1 iotop
31.2 Job Worker 1 2 jobservice: max_job_workers: 10
更多 Worker 意味着更多并发:
扫描;
复制;
GC;
Retention;
Webhook;
数据库连接;
磁盘 I/O。
不要只看队列长就把 Worker 从 10 改成 200。先判断瓶颈在 CPU、数据库、Trivy、存储还是上游网络。
31.3 数据库连接 单机 Harbor 内置 PostgreSQL。max_open_conns 不应无限增大,应结合:
Core 副本数;
Job Worker;
PostgreSQL max_connections;
管理和监控连接;
查询延迟;
事务等待。
31.4 日志 Harbor 日志路径:
检查:
1 2 du -sh /var/log/harbor find /var/log/harbor -type f -printf '%s %p\n' | sort -n | tail
31.5 不要将 Docker Build Cache 永久堆在 Harbor BuildKit Cache 可以推送到 OCI Registry:
1 2 3 4 5 6 7 8 docker buildx build \ --cache-to \ type =registry,ref=harbor.example.com/cache/example-app:buildcache,mode=max \ --cache-from \ type =registry,ref=harbor.example.com/cache/example-app:buildcache \ -t harbor.example.com/applications/example-app:1.0.0 \ --push \ .
Cache 项目应有独立 Retention,否则缓存会比正式镜像更擅长占磁盘。
第三部分:Distribution Registry 轻量 Docker 私服 三十二、适用场景 如果只需要:
1 2 3 4 5 Docker Push Docker Pull TLS Basic Auth 本地文件存储
可以部署 CNCF Distribution Registry 3.1.1。
它不提供 Harbor 的:
项目级 RBAC;
Robot Account;
Web UI;
Trivy 扫描;
Tag Retention;
Immutable Tag;
LDAP/OIDC;
跨 Registry 策略复制;
审计和治理界面。
三十三、创建 Registry 目录 1 2 mkdir -p /opt/distribution-registrymkdir -p /data/registry/{data,auth,certs}
创建用户:
1 2 3 4 5 6 7 htpasswd -B -C 12 -c \ /data/registry/auth/htpasswd \ registry-admin htpasswd -B -C 12 \ /data/registry/auth/htpasswd \ ci-publisher
权限:
1 2 3 chmod 0750 /data/registrychmod 0750 /data/registry/{data,auth,certs}chmod 0640 /data/registry/auth/htpasswd
复制证书:
1 2 /data/registry/certs/registry.example.com.crt /data/registry/certs/registry.example.com.key
三十四、Registry 配置 创建 /opt/distribution-registry/config.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 version: 0.1 log: level: info formatter: json storage: filesystem: rootdirectory: /var/lib/registry delete: enabled: true cache: blobdescriptor: inmemory maintenance: uploadpurging: enabled: true age: 168h interval: 24h dryrun: false http: addr: :5000 headers: X-Content-Type-Options: - nosniff tls: certificate: /certs/registry.example.com.crt key: /certs/registry.example.com.key debug: addr: 127.0 .0 .1 :5001 prometheus: enabled: true path: /metrics auth: htpasswd: realm: Company Registry path: /auth/htpasswd health: storagedriver: enabled: true interval: 10s threshold: 3
三十五、Registry Compose 创建 /opt/distribution-registry/compose.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 services: registry: image: registry:3.1.1 container_name: distribution-registry restart: unless-stopped ports: - "5000:5000" - "127.0.0.1:5001:5001" volumes: - ./config.yml:/etc/distribution/config.yml:ro,Z - /data/registry/data:/var/lib/registry:Z - /data/registry/auth:/auth:ro,Z - /data/registry/certs:/certs:ro,Z healthcheck: test: - CMD-SHELL - wget --no-check-certificate -qO- https://127.0.0.1:5000/v2/ >/dev/null || exit 1 interval: 30s timeout: 5s retries: 5 security_opt: - no -new-privileges:true ulimits: nofile: soft: 65536 hard: 65536 mem_limit: 2g cpus: 2.0
启动:
1 2 3 4 5 cd /opt/distribution-registry docker compose config docker compose pull docker compose up -d
防火墙:
1 2 firewall-cmd --permanent --add-port=5000/tcp firewall-cmd --reload
验证:
1 2 curl -u registry-admin \ https://registry.example.com:5000/v2/
正确结果:
客户端信任 CA:
1 2 3 4 5 6 7 mkdir -p \ /etc/docker/certs.d/registry.example.com:5000cp company-root-ca.crt \ /etc/docker/certs.d/registry.example.com:5000/ca.crt systemctl restart docker
登录与推送:
1 2 3 4 5 6 7 8 9 10 docker login registry.example.com:5000 docker pull busybox:1.37 docker tag \ busybox:1.37 \ registry.example.com:5000/base/busybox:1.37 docker push \ registry.example.com:5000/base/busybox:1.37
三十六、Distribution Registry 垃圾回收 Distribution Registry GC 是 Mark and Sweep。
必须先让 Registry 进入 Read Only 或完全停止,否则正在上传的 Layer 可能被错误回收,造成镜像损坏。
36.1 Dry Run 停止写入:
1 2 cd /opt/distribution-registry docker compose stop registry
执行 Dry Run:
1 2 3 4 5 6 7 docker run --rm \ -v /opt/distribution-registry/config.yml:/etc/distribution/config.yml:ro,Z \ -v /data/registry/data:/var/lib/registry:Z \ registry:3.1.1 \ garbage-collect \ --dry-run \ /etc/distribution/config.yml
执行正式 GC:
1 2 3 4 5 6 7 docker run --rm \ -v /opt/distribution-registry/config.yml:/etc/distribution/config.yml:ro,Z \ -v /data/registry/data:/var/lib/registry:Z \ registry:3.1.1 \ garbage-collect \ --delete-untagged \ /etc/distribution/config.yml
启动:
1 docker compose start registry
仅删除 Tag 不一定删除 Manifest。只有 Manifest 引用被删除,Blob 才有机会被 GC。
三十七、Distribution Registry Pull-through Cache Docker Hub Pull-through Cache 配置:
1 2 3 proxy: remoteurl: https://registry-1.docker.io ttl: 168h
如果配置 Docker Hub 私有账号:
1 2 3 4 5 proxy: remoteurl: https://registry-1.docker.io username: dockerhub-user password: dockerhub-token ttl: 168h
必须严格保护该 Mirror。否则这个账号能访问的 Docker Hub 私有镜像可能通过 Mirror 暴露给其他用户。
Docker Daemon:
1 2 3 4 5 { "registry-mirrors" : [ "https://mirror.example.com" ] }
1 2 jq . /etc/docker/daemon.json systemctl restart docker
Docker Daemon 的 Pull-through Mirror 主要面向 Docker Hub,不能把它当成任意 Registry 的通用透明镜像代理。
第四部分:客户端、CI/CD 与 Kubernetes 三十八、CI/CD 登录 Harbor 不要在命令行直接写明文密码:
1 2 3 4 printf '%s' "${HARBOR_ROBOT_SECRET} " \ | docker login harbor.example.com \ --username "${HARBOR_ROBOT_USERNAME} " \ --password-stdin
构建并推送:
1 2 3 4 5 6 7 IMAGE="harbor.example.com/applications/example-app:${GIT_COMMIT_SHA} " docker buildx build \ --platform linux/amd64 \ -t "${IMAGE} " \ --push \ .
流水线完成后可以:
1 docker logout harbor.example.com
CI 凭证应使用 Robot Account,且只授予目标项目所需的 Push/Pull 权限。
三十九、Kubernetes 使用 Harbor 39.1 containerd 信任 CA 在每个节点安装 CA:
1 2 3 4 cp company-root-ca.crt \ /etc/pki/ca-trust/source/anchors/company-root-ca.crt update-ca-trust
containerd 证书目录:
1 2 3 4 5 mkdir -p \ /etc/containerd/certs.d/harbor.example.comcp company-root-ca.crt \ /etc/containerd/certs.d/harbor.example.com/ca.crt
创建 /etc/containerd/certs.d/harbor.example.com/hosts.toml:
1 2 3 4 5 server = "https://harbor.example.com" [host."https://harbor.example.com"] capabilities = ["pull" , "resolve" , "push" ] ca = "/etc/containerd/certs.d/harbor.example.com/ca.crt"
确认 /etc/containerd/config.toml 启用了 Registry Config Path:
1 2 3 4 version = 2 [plugins."io.containerd.grpc.v1.cri".registry] config_path = "/etc/containerd/certs.d"
不同 containerd 版本的 Plugin 路径可能不同,应先执行:
1 2 containerd config dump containerd --version
重启:
1 systemctl restart containerd
测试:
1 2 3 ctr -n k8s.io images pull \ --user 'robot$production-puller:SECRET' \ harbor.example.com/base/nginx:1.30.4-alpine
39.2 ImagePullSecret 1 2 3 4 5 kubectl create secret docker-registry harbor-pull \ --docker-server=harbor.example.com \ --docker-username='robot$production-puller' \ --docker-password='ROBOT_SECRET' \ --namespace=production
Pod:
1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: Pod metadata: name: nginx namespace: production spec: imagePullSecrets: - name: harbor-pull containers: - name: nginx image: harbor.example.com/base/nginx:1.30.4-alpine
不要把 Robot Secret 直接写在 Git 中。推荐使用 External Secrets、Vault、Sealed Secrets 或 GitOps Secret 管理方案。
四十、npm 与 Harbor 联合流水线 flowchart LR
Git[Git Commit] --> CI[CI Pipeline]
CI --> NPMInstall[npm ci from Verdaccio]
NPMInstall --> Test[Test / Lint / Build]
Test --> Image[Build OCI Image]
Image --> HarborPush[Push to Harbor]
HarborPush --> Scan[Trivy Scan]
Scan --> Policy{Policy Pass?}
Policy -- Yes --> Deploy[Deploy by Digest]
Policy -- No --> Stop[Block Release]
示例流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 set -Eeuo pipefail npm ci npm test npm run build IMAGE="harbor.example.com/applications/example-app:${GIT_COMMIT_SHA} " docker buildx build \ --secret id =npmrc,src="$HOME /.npmrc" \ --platform linux/amd64,linux/arm64 \ -t "${IMAGE} " \ --push \ .
部署阶段通过 Harbor API 或工具取得 Digest,再以 Digest 发布。
第五部分:监控、调优与故障排查 四十一、健康检查 41.1 Verdaccio 1 2 curl -fsS \ https://npm.example.com/-/ping
41.2 Harbor 1 2 3 curl -fsS \ https://harbor.example.com/api/v2.0/health \ | jq
41.3 Distribution Registry 未认证时通常返回 401 也能说明 HTTPS 和 Registry API 已响应:
1 2 curl -I \ https://registry.example.com:5000/v2/
认证:
1 2 curl -u registry-admin \ https://registry.example.com:5000/v2/
四十二、建议监控指标 Verdaccio 1 2 3 4 5 6 7 8 9 10 进程/容器健康 HTTP 2xx/4xx/5xx 登录失败 npm publish 成功率 npm install P95/P99 npmjs Uplink 延迟和错误 Storage 容量 inode Node.js RSS 与 Event Loop 备份时间和结果
Harbor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 组件健康 Registry Push/Pull 成功率 Core API 延迟 PostgreSQL 连接与查询延迟 Redis 状态 Job Queue 长度 Trivy DB 更新时间 扫描任务成功率 复制任务状态 Retention 和 GC 状态 Blob 存储容量 审计日志 证书有效期 Robot Account 过期时间
Registry 1 2 3 4 5 6 7 /v2/ 可用性 Push/Pull 错误率 存储容量 上传临时目录 GC 结果 HTTP 401/5xx 证书有效期
四十三、系统调优 43.1 文件句柄 Docker 容器配置:
1 2 3 4 ulimits: nofile: soft: 65536 hard: 65536
宿主机:
1 2 3 4 5 6 7 cat > /etc/sysctl.d/99-registry.conf <<'EOF2' fs.file-max = 2097152 vm.swappiness = 1 net.core.somaxconn = 4096 EOF2 sysctl --system
不要复制大量来历不明的 TCP 参数。Registry 的瓶颈更常见于存储、网络、数据库、扫描任务和上游限流。
43.2 数据盘 推荐:
独立 SSD/NVMe;
XFS 或经过验证的 ext4;
预留 20% 以上空间;
监控 inode;
持续观察磁盘延迟;
避免备份、GC、扫描和大量 Push 同时执行。
1 2 3 4 iostat -xz 1 iotopdf -hT /datadf -ih /data
43.3 网络 镜像分发对网络吞吐敏感:
1 2 3 sar -n DEV 1 ss -s ethtool eth0
大量节点同时拉取时:
分批发布;
使用 Proxy Cache;
就近部署 Harbor;
多数据中心使用复制;
考虑对象存储重定向;
避免所有节点在整点同时更新。
四十四、常见问题:Verdaccio 44.1 容器无法读取 config.yaml 1 2 docker compose logs verdacciols -lnZ /data/verdaccio/conf
检查:
UID 10001;
GID 65533;
:Z;
文件名必须为 config.yaml;
YAML 缩进;
父目录执行权限。
44.2 npm login 失败 1 2 3 npm login \ --registry=https://npm.example.com/ \ --auth-type=legacy
检查:
1 2 3 npm config get registry npm config get @company:registry curl -I https://npm.example.com/-/ping
44.3 403 Publish Forbidden 检查:
包名是否是 @company/*;
当前用户是否已登录;
npm whoami;
publishConfig.registry;
Verdaccio packages 顺序;
当前包 Owner;
check_owners。
44.4 包找不到 1 2 3 npm view @company/utils \ --registry=https://npm.example.com/ \ --loglevel verbose
检查本地 Storage、Web UI、Scope 配置和客户端 .npmrc。
44.5 公共包下载慢 检查:
1 2 curl -I https://registry.npmjs.org/react getent hosts registry.npmjs.org
以及 Verdaccio 日志、DNS、代理出口和 npmjs 限流。
四十五、常见问题:Harbor 45.1 x509: certificate signed by unknown authority 1 2 3 4 openssl s_client \ -connect harbor.example.com:443 \ -servername harbor.example.com \ -showcerts
检查:
1 2 ls -l /etc/docker/certs.d/harbor.example.com/ca.crt update-ca-trust check
常见原因:
CA 没安装;
SAN 不含 Harbor 域名;
客户端使用 IP,但证书只有 DNS;
系统时间错误;
证书链不完整;
非 443 端口目录名缺少端口。
45.2 docker login 返回 401 检查:
用户名;
Robot Account 名称中的 $ 是否被 Shell 展开;
Secret;
项目权限;
Harbor Authentication Mode;
Token Service;
系统时间。
Robot 用户名应加单引号:
1 2 3 docker login \ --username 'robot$application-ci' \ harbor.example.com
45.3 Push Denied 1 denied: requested access to the resource is denied
检查镜像名称必须包含 Harbor Project:
1 harbor.example.com/applications/example-app:1.0.0
而不是:
1 harbor.example.com/example-app:1.0.0
同时检查 Robot 是否有 Push 权限、Tag 是否不可变、Project 是否超出 Quota。
45.4 Manifest Unknown 常见原因:
Tag 不存在;
Retention 已删除;
Proxy Cache 路径错误;
多架构 Manifest 未完整推送;
GC 或手工删除造成损坏;
项目名错误。
1 2 docker buildx imagetools inspect \ harbor.example.com/applications/example-app:1.0.0
45.5 Harbor UI 正常但 Push 超时 检查:
NGINX 上传限制;
Registry 日志;
数据盘;
网络 MTU;
防火墙;
客户端代理;
Harbor no_proxy;
Docker Daemon 日志。
1 2 3 journalctl -u docker -n 300 --no-pagercd /opt/harbor docker compose logs -f registry core nginx
45.6 Trivy 扫描失败 1 docker compose logs -f trivy-adapter
检查:
漏洞 DB 网络;
代理;
DNS;
数据目录;
内存;
Java DB;
离线配置;
Job Worker。
45.7 磁盘没有在删除镜像后释放 正确流程:
1 2 3 4 删除 Tag/Artifact 或执行 Retention -> 等待任务完成 -> GC Dry Run -> GC
Blob 仍被其他 Manifest 引用时不能回收。
45.8 Harbor 重启后组件不健康 1 2 3 cd /opt/harbor docker compose ps docker compose logs --tail =300
检查:
数据盘权限;
磁盘满;
PostgreSQL;
Redis;
证书;
harbor.yml;
Docker 网络;
升级迁移是否失败。
四十六、常见问题:Distribution Registry 46.1 Basic Auth + HTTP 无法使用 Distribution Registry 不支持在不安全 HTTP 上可靠使用 Basic Auth。生产必须配置 TLS。
46.2 405 Method Not Allowed 删除失败 需要:
1 2 3 storage: delete: enabled: true
重启 Registry 后生效。
46.3 GC 后镜像损坏 常见原因是在 Registry 仍允许上传时执行 Stop-the-world GC。
GC 前必须 Read Only 或停止 Registry,并先 Dry Run。
四十七、安全检查清单 Verdaccio
Harbor
Distribution Registry
四十八、生产选型建议
场景
推荐方案
npm 私有组件库
Verdaccio
npm 公共依赖代理缓存
Verdaccio
小型 Docker 私服
Distribution Registry
企业 Docker/OCI 私服
Harbor
需要漏洞扫描
Harbor + Trivy
需要 LDAP/OIDC、RBAC
Harbor
需要跨数据中心复制
Harbor
需要高可用 Docker Registry
Harbor Helm + Kubernetes
纯离线临时镜像中转
Distribution Registry
四十九、总结 在 Rocky Linux 10 上搭建 npm 和 Docker 私服,核心并不是简单启动两个容器,而是建立完整的制品治理链路:
1 2 3 4 5 6 7 8 9 10 11 源码 -> 依赖下载 -> 构建 -> 测试 -> npm 包或 OCI 镜像发布 -> 权限与签名 -> 漏洞扫描 -> 保留策略 -> 部署 -> 审计 -> 备份恢复
核心原则:
npm 私有包使用企业 Scope;
私有 Scope 不代理公网,降低 Dependency Confusion 风险;
Verdaccio 禁止匿名注册并使用短生命周期 Token;
npm Token 使用 CI Secret 和 BuildKit Secret;
Docker 生产私服优先选择 Harbor;
CI 使用 Robot Account,不使用 admin;
正式镜像使用不可变 Tag,并优先按 Digest 部署;
先 Retention,再 Garbage Collection;
Trivy 扫描不能替代镜像升级、签名和运行时安全;
Harbor Compose 是单主机部署,不要靠 compose scale 伪造高可用;
Distribution Registry GC 前必须停止写入;
制品仓库必须进行真实备份和恢复演练。
私服平时只是构建日志里一行不起眼的下载地址;一旦它出问题,前端装不了依赖、后端构建不了镜像、Kubernetes 拉不到版本,整个流水线会非常团结地一起停工。
参考资料