从 0 到 1 搭建私有 Docker 私服 Harbor

序言

  在容器化和微服务架构逐渐成为主流的背景下,镜像仓库已经成为 CI/CD 体系中不可或缺的基础组件。无论是应用的持续集成、持续交付,还是多环境(开发 / 测试 / 生产)的版本管理,稳定、可控、安全的镜像仓库都是保障交付效率与系统稳定性的关键。

  Harbor 是 CNCF 毕业项目之一,也是目前企业级私有镜像仓库中使用最为广泛的解决方案之一。相比原生 Docker Registry,Harbor 不仅提供了完整的 Web UI,还内置了 权限管理、项目隔离、镜像扫描、镜像复制、审计日志 等企业级能力,非常适合在内网或私有云环境中使用。

  本文将从零开始介绍 Harbor 在单机环境下的部署过程,重点覆盖以下内容:

  • Harbor 的下载、初始化配置与启动流程
  • 常见核心配置项的含义与修改建议
  • Docker 客户端与 Harbor 私服的对接方式(HTTP / HTTPS)
  • 镜像的推送、拉取及基础管理思路
  • 在实际生产环境中常见的扩展场景(如公网 HTTPS 访问)

  本文偏向实战与可落地,示例配置均来自真实部署场景,适合以下读者参考:

  • 正在搭建或计划搭建 CI/CD 平台的运维 / 后端工程师
  • 需要在内网部署私有 Docker 镜像仓库的团队
  • 希望快速了解 Harbor 部署与使用要点的技术人员

  如果你已经具备 Docker 和 Docker Compose 的基础使用经验,那么可以直接按照本文步骤进行部署;如果你是首次接触 Harbor,也可以将本文作为一份入门级部署指南逐步实践。

部署步骤

下载安装

1
2
3
4
5
cd /data/ci_cd
# 下载 harbor
wget https://github.com/goharbor/harbor/releases/download/v2.13.2/harbor-offline-installer-v2.13.2.tgz
tar xvf harbor-offline-installer-v2.13.2.tgz
mkdir -p /data/ci_cd/harbor/{data,log}

初始化配置

  对于配置文件,大概需要处理以下参数项:

  • hostname:修改为部署服务器的 ip 或域名
  • http.port: 修改为可用的端口,如 80 -> 28080
  • https: 关闭 https,将相关配置注释,如果需要公网配置,可以获取证书后进行相关配置
  • data_volume:修改挂载目录
  • log.location:修改挂载目录
  • 查看默认密码:在 harbor_admin_password 中默认密码为 Harbor12345
1
2
cd /data/ci_cd/harbor
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#hostname: reg.mydomain.com
hostname: 192.168.3.30

# http related config
http:
# port for http, default is 80. If https enabled, this port will redirect to https port
# port: 80
port: 28080

# https related config
#https:
# https port for harbor, default is 443
# port: 443
# The path of cert and key files for nginx
# certificate: /your/certificate/path
# private_key: /your/private/key/path
# enable strong ssl ciphers (default: false)
# strong_ssl_ciphers: false

# # Harbor will set ipv4 enabled only by default if this block is not configured
# # Otherwise, please uncomment this block to configure your own ip_family stacks
# ip_family:
# # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component
# ipv6:
# enabled: false
# # ipv4Enabled set to true by default, currently it affected the nginx related component
# ipv4:
# enabled: true

# # Uncomment following will enable tls communication between all harbor components
# internal_tls:
# # set enabled to true means internal tls is enabled
# enabled: true
# # put your cert and key files on dir
# dir: /etc/harbor/tls/internal


# Uncomment external_url if you want to enable external proxy
# And when it enabled the hostname will no longer used
# external_url: https://reg.mydomain.com:8433

# The initial password of Harbor admin
# It only works in first time to install harbor
# Remember Change the admin password from UI after launching Harbor.
harbor_admin_password: Harbor12345

# Harbor DB configuration
database:
# The password for the user('postgres' by default) of Harbor DB. Change this before any production use.
password: root123
# The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
max_idle_conns: 100
# The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections.
# Note: the default number of connections is 1024 for postgres of harbor.
max_open_conns: 900
# The maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's age.
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
conn_max_lifetime: 5m
# The maximum amount of time a connection may be idle. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's idle time.
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
conn_max_idle_time: 0

# The default data volume
data_volume: /data/ci_cd/harbor/data

trivy:
# ignoreUnfixed The flag to display only fixed vulnerabilities
ignore_unfixed: false
# skipUpdate The flag to enable or disable Trivy DB downloads from GitHub
#
# You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues.
# If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and
# `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path.
skip_update: false
#
# skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the
# `/home/scanner/.cache/trivy/java-db/trivy-java.db` path
skip_java_db_update: false
#
# The offline_scan option prevents Trivy from sending API requests to identify dependencies.
# Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it.
# For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't
# exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode.
# It would work if all the dependencies are in local.
# This option doesn't affect DB download. You need to specify "skip-update" as well as "offline-scan" in an air-gapped environment.
offline_scan: false
#
# Comma-separated list of what security issues to detect. Possible values are `vuln`, `config` and `secret`. Defaults to `vuln`.
security_check: vuln
#
# insecure The flag to skip verifying registry certificate
insecure: false
#
# timeout The duration to wait for scan completion.
# There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s.
timeout: 5m0s
#
# github_token The GitHub access token to download Trivy DB
#
# Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough
# for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000
# requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult
# https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting
#
# You can create a GitHub token by following the instructions in
# https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
#
# github_token: xxx

jobservice:
# Maximum number of job workers in job service
max_job_workers: 10
# Maximum hours of task duration in job service, default 24
max_job_duration_hours: 24
# The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB"
job_loggers:
- STD_OUTPUT
- FILE
# - DB
# The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`)
logger_sweeper_duration: 1 #days

notification:
# Maximum retry count for webhook job
webhook_job_max_retry: 3
# HTTP client timeout for webhook job
webhook_job_http_client_timeout: 3 #seconds

# Log configurations
log:
# options are debug, info, warning, error, fatal
level: info
# configs for logs in local storage
local:
# Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated.
rotate_count: 50
# Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes.
# If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G
# are all valid.
rotate_size: 200M
# The directory on your host that store log
location: /data/ci_cd/harbor/log

# Uncomment following lines to enable external syslog endpoint.
# external_endpoint:
# # protocol used to transmit log to external endpoint, options is tcp or udp
# protocol: tcp
# # The host of external endpoint
# host: localhost
# # Port of external endpoint
# port: 5140

#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY!
_version: 2.13.0

proxy:
http_proxy:
https_proxy:
no_proxy:
components:
- core
- jobservice
- trivy

upload_purging:
enabled: true
# remove files in _upload directories which exist for a period of time, default is one week.
age: 168h
# the interval of the purge operations
interval: 24h
dryrun: false

cache:
# not enabled by default
enabled: false
# keep cache for one day by default
expire_hours: 24

配置 docker-compose.yml

  执行如下脚本生成 docker-compose.yml 文件

1
2
cd /data/ci_cd/harbor
./prepare

启动 Harbor

1
docker-compose up -d

访问 Web 界面

  访问 http://your-server-ip:28080出现如下界面即代表 Harbor 安装成功!

登录界面

  注意,默认账户密码如下(一般建议更改,登录 web 界面后修改即可):

  • 用户:admin
  • 密码:Harbor12345

配置私服

  当我们安装完 harbor 私服后,其他安装了 docker 的服务器如果想从私服拉取镜像或者将镜像打包推送到私服,需要和私服打通。

  要想实现上述目标,需要在服务器上做几步配置,这大概分为三类情况:

  • 如果私服是 https + 合法证书:直接 docker login 就行
  • 如果私服是 http:需要先执行 docker login,之后配置 insecure-registries
  • 如果私服是 https+自签证书:需要先执行 docker login,之后 配置 ca.crt

https + 合法证书的私服

  首先,需要使用 docker login 命令登录私有仓库。这会将您的认证信息保存到服务器上,Docker 在拉取镜像时会自动使用这些信息。

1
docker login <私服地址>:<端口>

  然后,输入用户名、密码(登录成功后,凭证会保存在):

1
~/.docker/config.json

  拉取时直接:

1
docker pull <私服地址>:<端口>/<项目>/<镜像>:<tag>

登录命令格式说明

1
docker login [OPTIONS] [SERVER:PORT]
默认格式
1
2
# 如果你的私服使用的是标准端口(80/443),可以省略端口号
docker login your.private.registry.com

  执行后,命令行会提示您输入用户名和密码。

指定端口的私有仓库
1
2
# 如果您的私服运行在 5000 端口
docker login your.private.registry.com:5000
直接认证
1
2
# 使用命令行参数直接输入认证信息(适用于自动化脚本)
docker login your.private.registry.com -u <用户名> -p <密码>

  注意: 出于安全考虑,在生产环境中不建议直接使用 -p 参数,因为密码会出现在命令历史中。建议使用密码管理器或脚本从安全的地方获取密码。

  登录成功后,可以看到 Login Succeeded 的提示。您的认证信息会被加密保存在 /root/.docker/config.json 文件中。

http 协议的私服

  如果 Harbor 仓库使用的是 HTTP(而不是 HTTPS)或者使用了 自签名的 HTTPS 证书必须修改 Docker Daemon 的配置,告诉它信任私有仓库地址。否则,由于 Docker 默认只信任 HTTPS,会因安全原因拒绝连接。

  1. 编辑 Docker Daemon 的配置文件
1
sudo vi /etc/docker/daemon.json
  1. **在 daemon.json 文件中添加 insecure-registries
1
2
3
{
"insecure-registries": ["192.168.3.30:28080"]
}
  1. 保存文件并重启 Docker 服务。 修改配置后,必须重启 Docker 才能使更改生效。
1
sudo systemctl restart docker
  1. 验证配置是否生效
1
sudo docker info

  在输出的信息中,您应该能看到 Insecure Registries:Registry Mirrors: 部分包含您刚刚配置的地址。

私服是自签证书

  一般无此情况,不做说明。

推送镜像到私服

  假设我们现在仓库存在一个如下镜像需要推送到 docker 私服,必须给事先它打上包含私服地址和项目名称的特殊Tag,因为 Docker 客户端通过镜像的标签(Tag) 来确定这个镜像应该被推送到哪个仓库服务器。

1
2
REPOSITORY                          TAG                            IMAGE ID       CREATED          SIZE
visual-screen v1.0 779f8c67f2a9 19 minutes ago 529MB

  因此,我们首先需要将其重命名:

1
docker tag visual-screen:v1.0 192.168.3.30:28080/library/visual-screen:v1.0

  之后,才能成功推送到私服:

1
docker push 192.168.3.30:28080/library/visual-screen:v1.0

  由于以上操作过于繁琐,所以我们也可以在 docker build 构建镜像时,直接使用符合 Harbor 规范的完整镜像名作为 -t 参数的值。这样就能省去后续单独执行 docker tag 命令的步骤,一步到位。

扩展──镜像管理

  在 Harbor 中,我们需要根据具体的业务处理以下事务:

  • 学会使用过期的策略,镜像多久后会过期.
  • 镜像根据环境的不同覆盖或新增,对于测试环境和生产环境需要分别取进行处理:
    • 测试环境的镜像直接覆盖就行:此处我们的镜像使用固定版本就行
    • 生产环境的镜像可能需要注意回滚情况:此处我们的镜像根据 commit 来方便回滚

扩展──https访问

  上面部署的 Nginx 只能通过内网访问,如果希望通过公网访问,可以在公网服务器的 Nginx 上进行反向代理,实现公网 https 方式访问。

  要实现上述目标,需要修改两个配置,harbor.yml增加如下配置(harbor.xxx.com请修改为对应的公网域名)。

1
2
# external_url 必须是公网可访问的完整 URL
external_url: https://harbor.xxx.com

  公网 Nginx 配置如下(harbor.xxx.com请修改为对应的公网域名):

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
# harbor_backend 前缀同一代理
upstream harbor_backend {
server 192.168.3.30:28080;
}

server {
listen 443 ssl;
server_name harbor.xxx.com;

# 证书文件名称
ssl_certificate /etc/nginx/ssl/leeqingshui.com.crt;
# 私钥文件名称
ssl_certificate_key /etc/nginx/ssl/leeqingshui.com.key;
ssl_session_timeout 5m;
# 请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
# 请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
# 代理
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";

# Harbor 镜像较大,禁用 body 大小限制
client_max_body_size 0;

# Harbor 登录/上传需要 HTTP/1.1
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_buffering off;

# Harbor UI (portal)
location / {
proxy_pass http://harbor_backend;
}

# Harbor API、镜像仓库接口等都由同一端口处理
location /api/ {
proxy_pass http://harbor_backend;
}

location /service/ {
proxy_pass http://harbor_backend;
}

location /v2/ {
proxy_pass http://harbor_backend;
proxy_set_header Host $http_host; # Harbor 对 v2 的 host header 有严格要求
proxy_read_timeout 900;
proxy_send_timeout 900;
}

}

文章信息

时间 说明
2025-09-22 小结独立一文
0%