引言
在现代 devops 和云原生架构中,docker 已成为容器化部署的事实标准。而随着企业规模扩大和安全合规要求提升,搭建私有 docker 仓库(private docker registry)已成为刚需。本文将从零开始,手把手教你如何在 linux 系统上搭建一个功能完整、安全可靠、支持 https 和用户认证的私有镜像仓库,并提供 java 客户端操作示例。
一、为什么需要私有 docker 仓库?
虽然 docker hub 提供了海量公开镜像,但在生产环境中,我们通常面临以下问题:
- 安全性:业务镜像包含敏感代码或配置,不适合上传到公有平台。
- 网络延迟:从海外拉取镜像速度慢,影响 ci/cd 效率。
- 版本控制:需对镜像进行精细的版本管理和生命周期管理。
- 合规审计:企业需满足数据不出内网、镜像可追溯等合规要求。
私有仓库 = 镜像存储 + 访问控制 + 日志审计 + 高可用部署
二、环境准备
2.1 系统要求
- linux 发行版:ubuntu 20.04+ / centos 7+ / rocky linux 8+
- docker engine:20.10+
- docker compose:v2.0+
- 至少 2gb 内存
- 开放端口:5000(默认 registry)、443(https)、80(http重定向)
# 检查 docker 版本 docker --version # docker version 24.0.5, build ced0996 # 检查 docker compose docker compose version # docker compose version v2.20.2
2.2 域名与证书准备(可选但推荐)
为启用 https,你需要:
- 一个域名(如
registry.yourcompany.com) - ssl 证书(可使用 let’s encrypt 免费证书)
推荐使用 let’s encrypt 获取免费证书。
三、基础版:无认证本地仓库
最简单的私有仓库只需一条命令:
docker run -d \ -p 5000:5000 \ --restart=always \ --name local-registry \ -v /opt/registry/data:/var/lib/registry \ registry:2
启动后,即可推送本地镜像:
# 标记镜像 docker tag myapp:latest localhost:5000/myapp:latest # 推送镜像 docker push localhost:5000/myapp:latest # 拉取镜像 docker pull localhost:5000/myapp:latest
注意:此方式仅适用于测试环境!无认证、无加密、无日志!
四、进阶版:带用户认证的私有仓库
4.1 创建 htpasswd 用户文件
首先安装 httpd-tools 或 apache2-utils:
# ubuntu/debian sudo apt update && sudo apt install apache2-utils -y # centos/rhel sudo yum install httpd-tools -y
创建用户密码文件:
mkdir -p /opt/registry/auth htpasswd -bbn admin p@ssw0rd > /opt/registry/auth/htpasswd htpasswd -bbn devuser dev123 >> /opt/registry/auth/htpasswd
4.2 使用 docker-compose 编排服务
创建 docker-compose.yml:
version: '3.8'
services:
registry:
image: registry:2
restart: always
ports:
- "5000:5000"
environment:
registry_auth: htpasswd
registry_auth_htpasswd_realm: registry realm
registry_auth_htpasswd_path: /auth/htpasswd
registry_storage_filesystem_rootdirectory: /data
volumes:
- /opt/registry/data:/data
- /opt/registry/auth:/auth
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"启动服务:
docker compose up -d
4.3 测试登录与推送
# 登录私有仓库
docker login localhost:5000
# 输入用户名 admin,密码 p@ssw0rd
# 标记并推送
docker tag nginx:alpine localhost:5000/nginx:test
docker push localhost:5000/nginx:test
# 查看已推送镜像
curl -u admin:p@ssw0rd http://localhost:5000/v2/_catalog
# {"repositories":["nginx"]}
五、企业级:https + 域名 + nginx 反向代理
为了生产环境安全,必须启用 https。
5.1 准备 ssl 证书
假设你已申请好证书:
/etc/letsencrypt/live/registry.yourcompany.com/fullchain.pem/etc/letsencrypt/live/registry.yourcompany.com/privkey.pem
如果没有,可通过 certbot 申请:
sudo certbot certonly --standalone -d registry.yourcompany.com
5.2 配置 nginx 反向代理
创建 /etc/nginx/sites-available/docker-registry:
upstream docker-registry {
server 127.0.0.1:5000;
}
server {
listen 80;
server_name registry.yourcompany.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name registry.yourcompany.com;
ssl_certificate /etc/letsencrypt/live/registry.yourcompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/registry.yourcompany.com/privkey.pem;
ssl_protocols tlsv1.2 tlsv1.3;
ssl_ciphers high:!anull:!md5;
client_max_body_size 0;
chunked_transfer_encoding on;
location / {
proxy_pass http://docker-registry;
proxy_set_header host $http_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 $scheme;
proxy_read_timeout 900;
}
}启用配置并重启 nginx:
sudo ln -s /etc/nginx/sites-available/docker-registry /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx
5.3 修改 docker-compose.yml 支持外部访问
version: '3.8'
services:
registry:
image: registry:2
restart: always
environment:
registry_auth: htpasswd
registry_auth_htpasswd_realm: registry realm
registry_auth_htpasswd_path: /auth/htpasswd
registry_storage_filesystem_rootdirectory: /data
registry_http_host: https://registry.yourcompany.com
registry_http_secret: your_strong_secret_here_$(openssl rand -hex 32)
volumes:
- /opt/registry/data:/data
- /opt/registry/auth:/auth
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"registry_http_secret 是用于签名会话的密钥,建议每次部署随机生成。
六、监控与日志管理
6.1 启用访问日志
registry 默认记录 json 格式日志,可通过以下命令查看:
docker logs -f registry_registry_1
输出示例:
{
"go.version": "go1.19.4",
"http.request.host": "registry.yourcompany.com",
"http.request.id": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
"http.request.method": "get",
"http.request.remoteaddr": "203.0.113.42",
"http.request.uri": "/v2/",
"http.request.useragent": "docker/24.0.5 go/go1.20.7 git-commit/ced0996 kernel/5.15.0-76-generic os/linux arch/amd64",
"level": "info",
"msg": "response completed",
"time": "2024-05-20t10:00:00z"
}6.2 集成 prometheus 监控
修改 docker-compose.yml 添加监控端点:
environment: registry_http_debug_addr: :5001 registry_http_debug_prometheus_enabled: true registry_http_debug_prometheus_path: /metrics
然后通过 prometheus 抓取 http://registry:5001/metrics。
七、java 客户端操作私有仓库示例
虽然 docker cli 是主要操作工具,但在自动化系统、ci/cd 平台中,常需通过程序调用 registry api。下面是一个基于 java + apache httpclient 的完整示例。
7.1 maven 依赖
<dependencies>
<dependency>
<groupid>org.apache.httpcomponents.client5</groupid>
<artifactid>httpclient5</artifactid>
<version>5.2.1</version>
</dependency>
<dependency>
<groupid>com.fasterxml.jackson.core</groupid>
<artifactid>jackson-databind</artifactid>
<version>2.15.2</version>
</dependency>
<dependency>
<groupid>org.slf4j</groupid>
<artifactid>slf4j-simple</artifactid>
<version>2.0.7</version>
</dependency>
</dependencies>7.2 java 客户端类实现
import org.apache.hc.client5.http.classic.methods.*;
import org.apache.hc.client5.http.impl.classic.closeablehttpclient;
import org.apache.hc.client5.http.impl.classic.httpclients;
import org.apache.hc.client5.http.impl.io.poolinghttpclientconnectionmanager;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.entity.entityutils;
import org.apache.hc.core5.http.message.basicheader;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import java.io.ioexception;
import java.nio.charset.standardcharsets;
import java.util.base64;
public class dockerregistryclient {
private static final logger logger = loggerfactory.getlogger(dockerregistryclient.class);
private final string baseurl;
private final string username;
private final string password;
private final closeablehttpclient httpclient;
public dockerregistryclient(string baseurl, string username, string password) {
this.baseurl = baseurl.endswith("/") ? baseurl : baseurl + "/";
this.username = username;
this.password = password;
poolinghttpclientconnectionmanager cm = new poolinghttpclientconnectionmanager();
cm.setmaxtotal(100);
cm.setdefaultmaxperroute(20);
this.httpclient = httpclients.custom()
.setconnectionmanager(cm)
.build();
}
private string getauthheader() {
string credentials = username + ":" + password;
string encoded = base64.getencoder().encodetostring(credentials.getbytes(standardcharsets.utf_8));
return "basic " + encoded;
}
public boolean ping() {
try {
httpget request = new httpget(baseurl + "v2/");
request.addheader("authorization", getauthheader());
classichttpresponse response = httpclient.executeopen(null, request, null);
int status = response.getcode();
entityutils.consume(response.getentity());
return status == 200 || status == 401; // 401 表示需要认证,说明服务可达
} catch (exception e) {
logger.error("failed to ping registry", e);
return false;
}
}
public string listrepositories() throws ioexception {
httpget request = new httpget(baseurl + "v2/_catalog");
request.addheader("authorization", getauthheader());
try (classichttpresponse response = httpclient.executeopen(null, request, null)) {
if (response.getcode() != 200) {
throw new ioexception("http " + response.getcode() + ": " + response.getreasonphrase());
}
string body = entityutils.tostring(response.getentity(), standardcharsets.utf_8);
logger.info("repositories: {}", body);
return body;
}
}
public string listtags(string repository) throws ioexception {
httpget request = new httpget(baseurl + "v2/" + repository + "/tags/list");
request.addheader("authorization", getauthheader());
try (classichttpresponse response = httpclient.executeopen(null, request, null)) {
if (response.getcode() != 200) {
throw new ioexception("http " + response.getcode() + ": " + response.getreasonphrase());
}
string body = entityutils.tostring(response.getentity(), standardcharsets.utf_8);
logger.info("tags for {}: {}", repository, body);
return body;
}
}
public void deletetag(string repository, string reference) throws ioexception {
// 获取 manifest digest
string digest = getmanifestdigest(repository, reference);
if (digest == null) {
throw new ioexception("manifest not found for " + repository + ":" + reference);
}
httpdelete request = new httpdelete(baseurl + "v2/" + repository + "/manifests/" + digest);
request.addheader("authorization", getauthheader());
request.addheader("accept", "application/vnd.docker.distribution.manifest.v2+json");
try (classichttpresponse response = httpclient.executeopen(null, request, null)) {
int code = response.getcode();
if (code == 202) {
logger.info("successfully deleted {}@{}", repository, reference);
} else {
throw new ioexception("http " + code + ": " + response.getreasonphrase());
}
}
}
private string getmanifestdigest(string repository, string reference) throws ioexception {
httphead request = new httphead(baseurl + "v2/" + repository + "/manifests/" + reference);
request.addheader("authorization", getauthheader());
request.addheader("accept", "application/vnd.docker.distribution.manifest.v2+json");
try (classichttpresponse response = httpclient.executeopen(null, request, null)) {
header digestheader = response.getfirstheader("docker-content-digest");
return digestheader != null ? digestheader.getvalue() : null;
}
}
public void close() throws ioexception {
httpclient.close();
}
// 示例主函数
public static void main(string[] args) {
string registryurl = "https://registry.yourcompany.com";
string user = "admin";
string pass = "p@ssw0rd";
try (dockerregistryclient client = new dockerregistryclient(registryurl, user, pass)) {
system.out.println("📡 ping registry: " + client.ping());
system.out.println("\n📦 repositories:");
client.listrepositories();
system.out.println("\n🏷️ tags for 'myapp':");
client.listtags("myapp");
// 删除示例(谨慎使用)
// client.deletetag("myapp", "old-tag");
} catch (exception e) {
e.printstacktrace();
}
}
}7.3 输出示例
运行上述程序,你将看到类似输出:
📡 ping registry: true
📦 repositories:
{"repositories":["myapp","nginx","redis"]}
🏷️ tags for 'myapp':
{"name":"myapp","tags":["latest","v1.0","v1.1"]}此客户端支持:
- 仓库连通性检测
- 列出所有仓库
- 列出指定仓库的所有标签
- 删除指定镜像标签(需开启 delete 功能)
八、性能优化与高可用部署
8.1 存储驱动选择
registry 支持多种存储后端:
filesystem(默认,适合单机)s3(aws s3 或兼容对象存储)azure(azure blob storage)gcs(google cloud storage)swift(openstack swift)
示例:使用 minio s3 兼容存储
environment: registry_storage: s3 registry_storage_s3_accesskey: minio_access_key registry_storage_s3_secretkey: minio_secret_key registry_storage_s3_region: us-east-1 registry_storage_s3_bucket: docker-registry registry_storage_s3_regionendpoint: http://minio:9000 registry_storage_s3_encrypt: "false" registry_storage_s3_secure: "false"
8.2 启用缓存层
使用 redis 缓存频繁访问的元数据:
environment:
registry_cache_blobdescriptor: redis
registry_redis_addr: redis:6379
registry_redis_db: 0
registry_redis_pool_maxidle: 16
registry_redis_pool_maxactive: 64
registry_redis_pool_timeout: 300s
services:
redis:
image: redis:7-alpine
restart: always
volumes:
- redis-data:/data
volumes:
redis-data:8.3 负载均衡与多节点部署

所有节点共享同一存储后端和缓存,确保数据一致性。
九、ci/cd 集成示例(jenkins + shell)
在 jenkins pipeline 中集成私有仓库推送:
pipeline {
agent any
environment {
registry_url = "registry.yourcompany.com"
registry_cred = credentials('docker-registry-creds') // jenkins credentials id
image_name = "myapp"
image_tag = "${build_number}-${env.git_commit.take(8)}"
}
stages {
stage('build') {
steps {
sh 'docker build -t ${image_name}:${image_tag} .'
}
}
stage('login & push') {
steps {
sh '''
echo "$registry_cred_psw" | docker login $registry_url -u "$registry_cred_usr" --password-stdin
docker tag ${image_name}:${image_tag} $registry_url/${image_name}:${image_tag}
docker push $registry_url/${image_name}:${image_tag}
docker logout $registry_url
'''
}
}
stage('cleanup') {
steps {
sh 'docker rmi ${image_name}:${image_tag} $registry_url/${image_name}:${image_tag}'
}
}
}
post {
success {
echo "✅ image pushed successfully: $registry_url/$image_name:$image_tag"
}
failure {
echo "❌ build failed!"
}
}
}十、安全加固建议
10.1 启用内容信任(notary)
docker content trust(dct)确保镜像签名和完整性:
export docker_content_trust=1 export docker_content_trust_server=https://notary-server.yourcompany.com docker push registry.yourcompany.com/myapp:signed-v1
10.2 配置漏洞扫描(clair / trivy)
推荐使用 trivy 扫描镜像:
trivy image registry.yourcompany.com/myapp:latest
集成到 ci:
trivy image --exit-code 1 --severity critical registry.yourcompany.com/myapp:${image_tag}
10.3 网络隔离与防火墙
使用 firewalld 限制访问源:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="5000" accept' sudo firewall-cmd --reload
十一、常见问题排查
q1:unauthorized: authentication required
- 检查是否执行
docker login - 检查 htpasswd 文件权限:
chmod 644 /opt/registry/auth/htpasswd - 检查环境变量拼写:
registry_auth_htpasswd_path
q2:http: server gave http response to https client
docker 默认强制 https。若使用 http,需在 /etc/docker/daemon.json 中添加:
{
"insecure-registries": ["registry.yourcompany.com:5000"]
}然后重启 docker:
sudo systemctl restart docker
q3: 推送大镜像失败
调整 nginx 和 registry 的超时设置:
# nginx 配置中增加 proxy_read_timeout 1200; proxy_send_timeout 1200;
# docker-compose.yml environment: registry_http_timeout: 1200s
十二、扩展功能:web ui 管理界面
官方 registry 不提供 web 界面,可集成第三方 ui:
推荐项目:portus 或 harbor(推荐后者)
harbor 是 cncf 毕业项目,功能完整:
- 图形化镜像管理
- 多租户权限控制
- 漏洞扫描集成
- helm chart 支持
- 审计日志
部署 harbor 参考官方文档:https://goharbor.io/docs/
harbor = registry + clair + notary + portal + rbac + replication
十三、设计理念与架构解析
docker registry v2 遵循 restful 设计,核心概念:
- repository(仓库):一组相关镜像集合,如
library/nginx - tag(标签):指向特定 manifest 的别名,如
latest,v1.0 - manifest(清单):描述镜像结构的 json 文件,包含层列表和配置
- blob(层):实际的文件系统层,内容寻址存储(sha256)

每次推送,docker 客户端会先上传所有层(blob),最后提交 manifest。
十四、镜像清理策略
registry 默认不自动清理,需手动或定时删除。
14.1 启用删除功能
在 config.yml 或环境变量中启用:
environment: registry_storage_delete_enabled: "true"
14.2 使用 gc 清理未引用层
# 进入容器 docker exec -it registry_registry_1 /bin/sh # 执行垃圾回收(需停止写入) registry garbage-collect /etc/docker/registry/config.yml
14.3 自动化清理脚本
#!/bin/bash
# cleanup-old-images.sh
registry_url="https://registry.yourcompany.com"
user="admin"
pass="p@ssw0rd"
repos=$(curl -s -u $user:$pass $registry_url/v2/_catalog | jq -r '.repositories[]')
for repo in $repos; do
tags=$(curl -s -u $user:$pass $registry_url/v2/$repo/tags/list | jq -r '.tags[]')
count=0
for tag in $tags; do
((count++))
if [ $count -gt 10 ]; then
echo "🗑️ deleting $repo:$tag"
# 调用前面 java 客户端的 deletetag 方法或使用 curl
digest=$(curl -s -i -u $user:$pass -h "accept: application/vnd.docker.distribution.manifest.v2+json" \
$registry_url/v2/$repo/manifests/$tag | grep docker-content-digest | cut -d' ' -f2 | tr -d '\r')
curl -x delete -u $user:$pass $registry_url/v2/$repo/manifests/$digest
fi
done
done
# 触发 gc
docker exec registry_registry_1 registry garbage-collect /etc/docker/registry/config.yml
十五、总结
搭建私有 docker 仓库不是“一次性工程”,而是持续演进的基础设施。从最简单的 registry:2 单容器部署,到支持 https、认证、高可用、漏洞扫描的企业级方案,每一步都应根据团队规模和安全需求逐步推进。
本文提供的 java 客户端可无缝集成到你的运维平台、ci/cd 系统或镜像管理后台中,实现自动化镜像生命周期管理。
记住几个关键原则:
🔒 安全第一 —— 强制 https + 认证
📈 可观测性 —— 日志 + 监控 + 告警
♻️ 自动化 —— 自动构建 + 自动扫描 + 自动清理
🌐 标准化 —— 统一命名规范 + tag 策略 + 多环境镜像同步
现在,是时候告别 docker save/load 的原始时代,拥抱现代化的私有镜像仓库体系了!
下一步行动建议:
- 在测试环境部署基础版
- 编写 java 客户端连接测试
- 申请域名和证书升级 https
- 集成到 jenkins/gitlab ci
- 设置定期镜像清理任务
以上就是linux搭建docker私有仓库的方法步骤的详细内容,更多关于linux搭建docker私有仓库的资料请关注代码网其它相关文章!
发表评论