当前位置: 代码网 > 服务器>服务器>Linux > Linux搭建Docker私有仓库的方法步骤

Linux搭建Docker私有仓库的方法步骤

2026年04月21日 Linux 我要评论
引言在现代 devops 和云原生架构中,docker 已成为容器化部署的事实标准。而随着企业规模扩大和安全合规要求提升,搭建私有 docker 仓库(private docker registry)

引言

在现代 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-toolsapache2-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 的原始时代,拥抱现代化的私有镜像仓库体系了!

下一步行动建议:

  1. 在测试环境部署基础版
  2. 编写 java 客户端连接测试
  3. 申请域名和证书升级 https
  4. 集成到 jenkins/gitlab ci
  5. 设置定期镜像清理任务

以上就是linux搭建docker私有仓库的方法步骤的详细内容,更多关于linux搭建docker私有仓库的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com