Skip to content

构建镜像推送私有仓库

约 1535 字大约 5 分钟

docker

2025-12-03

前言

本地开发项目经常需要修改代码,之后重新构建镜像,然后推送到镜像仓库,接着在部署机上面拉取新镜像重建容器。但是默认情况下是推送到 DockerHub 的,每次走公网实在是没有必要,因此在内网中部署一个镜像仓库是很有必要的。

1. 反向代理

因为 docker 默认是不信任 http 请求的,可以修改配置让它信任,但是我的内网环境下已经有了 Caddy 反代服务,并且也有了 https 证书,所以就规范一些,分配一个子域名给镜像仓库服务:dcoker.mayee.example.com。反代配置如下(192.168.0.100:5000 为镜像仓库的地址):

Caddyfile
# 原有配置略 ...
dcoker.mayee.example.com {
    # 部署镜像仓库服务(Registry)的 ip 和 port
    reverse_proxy 192.168.0.100:5000 {
        # 关键配置:传递 Host 和 Proto 头
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-For {remote}
        header_up X-Forwarded-Proto {scheme}
    }
}
# 原有配置略 ...

然后重启 Caddy 容器,使配置生效。

2. 部署镜像仓库

由于我的需求是纯内网环境下,因此不考虑安全和鉴权的问题,简单使用即可。所以选择了registry和相应的 UI 服务docker-registry-ui

compose.yaml
version: '3'

services:
  registry:
    image: registry:latest
    container_name: registry
    restart: always
    ports:
      - "5000:5000"
    environment:
      # ====== 基础配置 ======
      REGISTRY_STORAGE_DELETE_ENABLED: "true"
      REGISTRY_HTTP_SECRET: "mayee"
      # ====== 日志与监控优化 ======
      OTEL_TRACES_EXPORTER: "none"
      REGISTRY_LOG_LEVEL: "warn"
      # ====== ⚠️ CORS 跨域配置 (UI 必须) ======
      # 允许所有域名访问 (内网环境为了方便直接开 *)
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: "['*']"
      # 允许的方法
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: "['HEAD', 'GET', 'OPTIONS', 'DELETE']"
      # 允许的 Header
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: "['Authorization', 'Accept', 'Cache-Control']"
      # 允许前端读取的 Header (比如读取 Docker 的 Digest)
      REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: "['Docker-Content-Digest']"

    volumes:
      - ./data:/var/lib/registry

  registry-ui:
    image: joxit/docker-registry-ui:main
    container_name: registry-ui
    restart: always
    ports:
      - "15000:80"
    environment:
      - REGISTRY_URL=https://dcoker.mayee.example.com
      - REGISTRY_TITLE=Mayee Registry
      - DELETE_IMAGES=true
      - SINGLE_REGISTRY=true
    depends_on:
      - registry

运行起来即可,访问部署机http://192.168.0.100:15000即可看到 ui 页面。

这里我们允许通过页面删除镜像,但是 Docker Registry 的一个经典机制:UI 上点击删除,只是删除了“引用”(Tag 和 Manifest),也就是相当于删除了文件的“快捷方式”,但实际的数据块(Blobs/Layers)依然躺在硬盘里占用空间。Docker 官方这样设计是为了防止并发读写时的冲突,但对于我们个人 NAS 来说,这就是单纯的“占着茅坑不拉屎”,所以得“办了它”。

可以直接手动执行docker exec -i registry bin/registry garbage-collect --delete-untagged /etc/docker/registry/config.yml,如果提示/etc/docker/registry/config.yml文件找不到,可以用docker exec -it registry find / -name "config.yml"命令找出文件的位置。例如我的文件真实位置在/etc/distribution/config.yml,替换为真实文件位置后再次执行。

但总是手动执行是麻烦的,可以用定时任务来执行。而有的系统,例如:飞牛(FnOS)。它安装的 docker 不是 root 用户组的,导致在普通用户下执行 docker 命令需要加 sudo。我们也可以把 docker 加入到 root 组,但是为了不破坏系统原本的设计用意,还是选择以 root 身份来定时执行脚本。

  1. 切换到 root 用户sudo -i
  2. 创建任务脚本/root/docker-gc.sh
docker-gc.sh
#!/bin/bash
echo "=========================================="
echo "Starting Docker Registry GC at $(date)"
# 1. 执行垃圾回收
# --delete-untagged: 清理未被标签引用的孤儿层(慎用,但在个人开发环境通常很有用)
/usr/bin/docker exec -i registry bin/registry garbage-collect --delete-untagged /etc/distribution/config.yml
# 2. (可选) 重启 Registry 容器
# 某些版本的 Registry 在 GC 后如果不重启,统计数据可能不更新,或者不释放文件句柄
/usr/bin/docker restart registry
echo "Finished Docker Registry at $(date)"
echo "=========================================="
  1. 赋予执行权限chmod +x /root/docker-gc.sh
  2. 创建定时任务,执行crontab -e会打开定时任务的 nano 编辑器,在新的一行添加0 4 * * 1 /bin/bash /root/docker-gc.sh >> /var/log/registry-gc.log 2>&1,就会在每周一的凌晨 4:00 整执行一次,如果推送到比较频繁,可以调小执行周期。之后按下ctrl+xy`保存即可。
  3. 退出 root 用户`exit。

这样一来,当通过 ui 页面删除了镜像,那些镜像就成了没有标签的孤儿层,之后脚本触发时会扫描到这些镜像,然后从硬盘上真正删除。

3. 开发中使用

由于 docker 的网站被墙了,通常需要配置国内镜像源。开发机上可以编辑/etc/docker/daemon.json,加上镜像源地址,保存后重启 docker sudo systemctl restart docker。开发构建镜像时,为了构建多平台镜像会使用buildx,但 buildx 构建过程是在一个独立的容器里进行的,它默认不读取宿主机的daemon.json,所以我们得给它一个配置用,配置好国内镜像源。

  1. 在项目根目录下创建文件buildkitd.toml
buildkitd.toml
debug = false

[registry."docker.io"]
  # 国内镜像源,或者你的阿里云加速地址
  mirrors = ["https://docker.m.daocloud.io", "https://dockerproxy.net"]

[registry."dcoker.mayee.example.com"]
  http = false
  insecure = false
  1. 镜像构建脚本docker.sh,一次性推送到公有合私有仓库,也可以去掉推送公有仓库,只推私仓。
docker.sh
#!/bin/bash
set -e

echo "🔨 [1/2] 构建 Jar 包 ..."
# maven 离线模式打包,跳过测试
./mvnw clean package -o -DskipTests
if [ $? -ne 0 ]; then echo "❌ 构建 Jar 包失败"; exit 1; fi

echo "🐳 [2/2] 构建 Docker 镜像 ..."
# 确保创建了 buildx 实例 (只需运行一次,如果已存在会跳过)
docker buildx create --name builderx --use --driver docker-container --config buildkitd.toml 2>/dev/null || true
docker buildx inspect --bootstrap
# Buildx 特性,打多架构镜像必须直接 push 到仓库,不能保存在本地 images 列表
docker buildx build \
    --platform linux/arm64,linux/amd64 \
    -f Dockerfile \
    -t dcoker.mayee.example.com/mayeee/homebox:2.0.0 \
    -t dcoker.mayee.example.com/mayeee/homebox:latest \
    -t mayeee/homebox:2.0.0 \
    -t mayeee/homebox:latest \
    --push .
  1. Dockerfile 示例。
FROM eclipse-temurin:25-jre-alpine
# 安装运行时依赖
RUN apk add --no-cache fping openssh-client openssl tzdata
# 创建运行目录
WORKDIR /app
# 从 builder 镜像拷贝 jar
COPY target/homebox.jar ./homebox.jar
# 暴露端口
EXPOSE 8000
# 启动命令
ENTRYPOINT ["java", "-jar", "homebox.jar"]

如此,即可实现纯内网环境下开发和部署私有项目,并且用国内镜像源无网络问题干扰。