Linux搭建Docker私有仓库的方法步骤
作者:知远漫谈
引言
在现代 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私有仓库的资料请关注脚本之家其它相关文章!
