使用官方 Registry v2 搭建 Docker Mirror 私有镜像仓库

在国内网络环境下,直接从 Docker Hub 拉取镜像经常遇到超时或被限流的问题。搭建一个本地 Docker Mirror 代理仓库可以有效解决这个痛点。本文介绍如何使用官方 Registry v2 搭建私有镜像代理仓库。

为什么要搭 Docker Mirror

1. Docker Hub 公网限速

Docker Hub 对匿名用户的拉取限速为 100 次/6 小时,免费账户为 200 次/6 小时。在 CI/CD 流水线或多人团队环境下,这个限额很容易触达。通过自建 Mirror 代理,所有节点的拉取请求汇聚到 Mirror 节点,Mirror 对上游只产生一次请求,有效规避限额。

2. 缓存加速 Build

在 CI/CD 构建中,每次 docker build 都需要拉取基础镜像。即使代码没有变化,基础镜像也会被反复从公网拉取,浪费时间。Mirror 缓存了基础镜像后:

  • 首次构建:从上游拉取并缓存
  • 后续构建:直接从 Mirror 拉取,速度提升 5-10 倍
  • 多节点并行构建:共享同一个 Mirror,只消耗一次外网流量

3. 离线可用与稳定性

特性说明
离线可用已缓存的镜像在内网环境下随时可用,不依赖外网连接
稳定可控不受 Docker Hub 宕机、限速、DNS 污染等公网问题影响
节省带宽多个节点共享缓存,外网出口带宽消耗大幅降低

架构说明

Docker Client → Caddy (HTTPS) → Registry v2 (Mirror) → DaoCloud 上游 → Docker Hub
                              本地缓存(命中则直接返回)

整个链路的请求流程:

  1. Docker Client 向 docker pull 配置的 Mirror 地址发起拉取请求
  2. 请求经 Caddy HTTPS 反向代理转发到 Registry v2
  3. Registry 检查本地缓存,命中则直接返回
  4. 缓存未命中时,Registry 向 DaoCloud 上游代理转发请求
  5. DaoCloud 再从 Docker Hub 获取镜像数据并逐层返回
  6. Registry 将收到的每一层数据写入本地缓存

环境准备

  • Docker 20.10+:确保 Docker Engine 版本支持 registry-mirrors 配置
  • 域名:一个已解析到服务器 IP 的域名,用于 Caddy 自动申请 HTTPS 证书(如 mirror.example.com
  • SSD 存储:建议至少 100GB 以上,镜像层文件较大且频繁读写,SSD 能显著提升缓存命中后的响应速度
  • 开放端口:只需开放 443 端口(HTTPS),Registry 本身通过 Docker 内部网络通信,不暴露端口到宿主机

部署 Registry v2 Mirror

配置文件

创建 config.yml

# Registry 配置文件版本,当前为 0.1
version: 0.1

# 日志配置
log:
  # 日志级别:debug / info / warn / error / fatal,默认 info
  level: info
  # 日志格式:json / text / logfmt,默认 logfmt
  formatter: logfmt
  # 附加字段,会添加到每条日志中,默认无
  fields:
    service: registry
    environment: production

# 存储配置
storage:
  # 存储驱动类型,默认 filesystem
  filesystem:
    # 镜像层数据的存储根目录,默认 /var/lib/registry
    rootdirectory: /var/lib/registry
    # 并发文件系统操作的线程数,默认 100
    maxthreads: 100
  # Blob 描述符缓存,用于加速 manifest 请求
  cache:
    blobdescriptor: inmemory
  # 启用删除 API,允许通过 DELETE 接口删除 blob,默认 false
  delete:
    enabled: true
  # 存储维护任务配置
  maintenance:
    # 上传清理,自动清理未完成的上传文件
    uploadpurging:
      # 是否启用上传清理,默认 true
      enabled: true
      # 未完成上传文件的保留时间,超过此时间将被清理,默认 168h(7 天)
      age: 168h
      # 清理检查间隔,默认 24h
      interval: 24h
      # 是否只预览不实际删除,默认 false
      dryrun: false
    # 只读模式开关,设为 true 时拒绝所有写入操作(push、delete),拉取正常
    # 可用于 GC 时临时开启,防止并发写入,默认 false
    readonly:
      enabled: false

# HTTP 服务器配置
http:
  # 监听地址和端口,默认 :5000
  addr: :5000
  # 监听的网络接口,默认 localhost(仅本地访问)
  host: 0.0.0.0
  # HTTP 响应头设置
  headers:
    # 防止浏览器 MIME 类型嗅探,安全最佳实践
    X-Content-Type-Options: [nosniff]
    # CORS 允许的来源,设为 * 允许所有来源
    Access-Control-Allow-Origin: ['*']
    # CORS 允许的 HTTP 方法
    Access-Control-Allow-Methods:
      - HEAD
      - GET
      - OPTIONS
    # CORS 允许的请求头
    Access-Control-Allow-Headers:
      - Authorization
      - Accept

# 上游代理配置
proxy:
  # 上游镜像源地址(必填)
  remoteurl: https://docker.m.daocloud.io
  # 上游认证用户名(可选,公共源不需要)
  # username:
  # 上游认证密码(可选,公共源不需要)
  # password:

# 健康检查配置
health:
  storagedriver:
    # 是否启用存储健康检查(默认:true)
    enabled: true
    # 检查间隔(默认:10s)
    interval: 10s
    # 连续失败多少次后标记不健康(默认:3)
    threshold: 3

关键配置项说明

配置项默认值说明
storage.filesystem.rootdirectory/var/lib/registry镜像存储路径
storage.cache.blobdescriptorinmemoryBlob 元数据缓存方式,可选 redis
storage.delete.enabledfalse必须设为 true,否则无法执行 GC
storage.maintenance.readonly.enabledfalse只读模式,GC 时临时开启
proxy.remoteurl-上游镜像源地址
health.storagedriver.enabledtrue存储驱动健康检查

上游源选择:DaoCloud

特性说明
稳定性DaoCloud 是国内主流容器厂商,镜像站长期维护
完整性同步 Docker Hub 全量镜像
速度国内 CDN 加速
免费公共服务,无需注册

其他可选的上游源:

# DaoCloud
remoteurl: https://docker.m.daocloud.io

# 阿里云
remoteurl: https://registry.cn-hangzhou.aliyuncs.com

# 腾讯云
remoteurl: https://mirror.ccs.tencentyun.com

可以部署多个 Mirror 实例使用不同的上游源,互为备份。

缓存类型

Registry v2 的 proxy 模式使用以下缓存机制:

1. Blob 缓存(层缓存)

镜像的每一层(layer)都会被缓存到本地存储:

/var/lib/registry/
├── docker/
   └── registry/
       └── v2/
           ├── blobs/
              └── sha256/          # 镜像层数据
           └── repositories/        # 仓库元数据

2. Manifest 缓存(清单缓存)

镜像的 manifest(包含层关系、tag 等信息)也会被缓存。

3. BlobDescriptor 缓存(元数据缓存)

配置 storage.cache.blobdescriptor

# 内存缓存(默认,适合中小规模)
storage:
  cache:
    blobdescriptor: inmemory

# Redis 缓存(适合大规模部署)
storage:
  cache:
    blobdescriptor: redis
redis:
  addr: redis:6379
  password: yourpassword
  db: 0
  ringlimit: 100

缓存策略

缓存类型说明建议场景
inmemory元数据存内存,blob 存磁盘镜像数量 < 10000
redis元数据存 Redis,blob 存磁盘镜像数量 > 10000,多实例

缓存未命中

  1. Registry 检查本地存储 → 未找到
  2. 转发请求到上游(DaoCloud)
  3. 上游返回数据 → Registry 同时缓存到本地并返回给客户端
  4. 后续请求直接从本地返回

GC(垃圾回收)

随着时间推移,Registry 中会积累大量不再使用的镜像层数据(例如旧版本镜像被更新替换)。GC 用于清理这些无用数据,释放磁盘空间。

GC 原理

Registry 使用 content-addressable 存储,每个 blob 通过 SHA256 哈希标识。GC 的核心逻辑:

  1. 标记所有被当前 manifest 引用的 blob
  2. 删除未被引用的 blob(垃圾数据)
manifest v2 → 引用 blob A, B, C
manifest v1 → 引用 blob A, B, D(旧版本)

GC 后:blob D 被清除(只被旧 manifest 引用)

在线 GC(read-only 模式)

Registry v2 支持 只读模式(read-only mode),GC 前切换为只读,避免并发写入导致数据不一致:

  1. 将 Registry 切换为只读模式(拒绝写入,拉取正常)
  2. 执行 GC 清理未引用的 blob
  3. 恢复读写模式

第一步:预览(dry-run)

# 安全操作,不影响服务,建议先执行确认
docker exec registry-mirror bin/registry garbage-collect \
  /etc/docker/registry/config.yml \
  --dry-run

输出示例:

ubuntu for sha256:abc123...
nginx for sha256:def456...

第二步:切换只读 + 执行 GC + 恢复

注意:不能通过环境变量 REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED=true 设置只读模式,会触发 panic(Registry 的已知 bug,环境变量覆盖 maintenance 子配置时 map key 类型不匹配)。 正确做法是通过 docker exec 修改容器内的 config.yml,再用 docker restart 重载配置

# 1. 开启只读模式 + 重启生效
docker exec registry-mirror sed -i "/readonly:/,/enabled:/ s/enabled: false/enabled: true/" /etc/docker/registry/config.yml
docker restart registry-mirror

# 2. 等待容器就绪后执行 GC
sleep 5
docker exec registry-mirror bin/registry garbage-collect /etc/docker/registry/config.yml

# 3. 恢复读写模式 + 重启生效
docker exec registry-mirror sed -i "/readonly:/,/enabled:/ s/enabled: true/enabled: false/" /etc/docker/registry/config.yml
docker restart registry-mirror

定期 GC 建议

建议通过 cron 定期执行 GC:

# 每月 1 号凌晨 3 点执行 GC
0 3 1 * * root docker exec registry-mirror sed -i "/readonly:/,/enabled:/ s/enabled: false/enabled: true/" /etc/docker/registry/config.yml && docker restart registry-mirror && sleep 5 && docker exec registry-mirror bin/registry garbage-collect /etc/docker/registry/config.yml && docker exec registry-mirror sed -i "/readonly:/,/enabled:/ s/enabled: true/enabled: false/" /etc/docker/registry/config.yml && docker restart registry-mirror

GC 与删除 API 的区别

方式作用用途
DELETE /v2/<name>/manifests/<digest>删除指定 manifest删除特定镜像 tag
garbage-collect清理未引用的 blob回收磁盘空间

两者通常配合使用:先通过 DELETE API 删除不需要的 manifest,再执行 GC 释放空间。

Caddy Server — HTTPS 反向代理

Docker 客户端配置 registry-mirrors 要求使用 HTTPS。使用 Caddy 的核心优势是 零配置自动 HTTPS,自动申请和续期 Let’s Encrypt 证书。

为什么选 Caddy

特性CaddyNginx
HTTPS 证书自动申请续期需配合 certbot
配置复杂度极简较复杂
HTTP/3原生支持需额外编译模块
适合场景中小规模、快速部署大规模、复杂路由

Caddyfile 配置

创建 Caddyfile

mirror.example.com {
    reverse_proxy registry:5000

    # 上传大镜像需要放宽限制
    request_body {
        max_size 10GB
    }

    # Docker 客户端需要的 header
    header /v2/ Docker-Distribution-Api-Version "registry/2.0"

    # 访问日志
    log {
        output file /data/caddy/access.log
        format console
    }

    # 健康检查端点不记日志
    @health path /v2/
    log @health {
        output discard
    }
}

确保域名 mirror.example.com 的 DNS A 记录已指向服务器公网 IP,Caddy 才能自动申请 Let’s Encrypt 证书。

完整 docker-compose.yml(Caddy + Registry)

version: '3.8'

services:
  caddy:
    image: caddy:2-alpine
    container_name: registry-caddy
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"    # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy_data:/data
      - ./caddy_config:/config
    depends_on:
      registry:
        condition: service_healthy

  registry:
    image: registry:2
    container_name: registry-mirror
    restart: always
    # 不暴露端口,仅通过 Caddy 访问
    expose:
      - "5000"
    volumes:
      - ./data:/var/lib/registry
      - ./config.yml:/etc/docker/registry/config.yml
    environment:
      - REGISTRY_STORAGE_DELETE_ENABLED=true
      - REGISTRY_HTTP_ADDR=0.0.0.0:5000
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:5000/v2/"]
      interval: 30s
      timeout: 10s
      retries: 3

启动服务

# 启动
docker compose up -d

# 查看 Caddy 日志,确认证书申请成功
docker logs -f registry-caddy

看到类似输出表示 HTTPS 证书已自动获取:

{"level":"info","msg":"certificate obtained successfully","identifier":"mirror.example.com"}

验证

# 测试 HTTPS 访问
curl https://mirror.example.com/v2/

# 应返回空 JSON:{}

Docker 客户端配置

{
  "registry-mirrors": [
    "https://mirror.example.com"
  ],
  "insecure-registries": []
}

使用 Caddy 提供 HTTPS 后,不需要 配置 insecure-registries,安全性更好。

Caddy 数据持久化

./caddy_data/     # TLS 证书(自动申请、自动续期)
./caddy_config/   # Caddy 配置缓存

证书续期由 Caddy 自动处理,无需人工干预。

日常运维

查看存储占用

# 查看缓存目录大小
du -sh /data/registry/

# 查看各仓库占用
du -sh /data/registry/docker/registry/v2/repositories/*

查看日志

docker logs -f --tail 100 registry-mirror
docker logs -f --tail 100 registry-caddy

健康检查

# 检查服务状态
curl https://mirror.example.com/v2/

# 检查上游连通性
curl https://mirror.example.com/v2/_catalog

完整目录结构

docker-mirror/
├── docker-compose.yml      # Caddy + Registry
├── Caddyfile               # Caddy HTTPS 反向代理配置
├── config.yml              # Registry 配置
├── data/                   # Registry 镜像缓存
├── caddy_data/             # Caddy TLS 证书
└── caddy_config/           # Caddy 配置缓存

总结

要点说明
核心组件官方 Registry v2 + proxy 模式
HTTPS 代理Caddy(零配置自动 HTTPS、证书自动续期)
上游源DaoCloud(稳定、完整、免费)
缓存加速Blob 磁盘缓存 + 元数据内存/Redis 缓存,加速 CI/CD Build
GCread-only 模式 + docker exec,防止并发写入
运维要点监控磁盘用量、定期 GC、关注上游可用性

对于内网 Docker 环境,搭建一个 Mirror 代理仓库是性价比极高的基础设施投入,一次部署长期受益。