Dokploy 实战:搭建自带“自动清理”功能的私有 Docker 镜像仓库 — 20260225
在个人服务器上搭建私有 Docker Registry(镜像仓库)是很多开发者的刚需。使用 Dokploy 部署虽然简单,但很多人很快会遇到一个棘手的问题:磁盘空间爆炸。
原生的 Docker Registry 像个“只进不出”的貔貅,默认既不支持删除镜像,删除了也不释放空间。
本文将带你通过 Dokploy 部署一个带 Web 管理界面、支持删除且每晚自动释放磁盘空间的完美私有仓库。
1. 为什么 Registry 的清理这么麻烦?
在开始配置之前,我们需要理解为什么 Docker 官方把 Registry 设计得这么“难用”。很多人发现,即便开启了删除功能,删除了镜像,磁盘占用依然没变。
这是因为 Registry 的清理机制分为两步:
- 软删除 (Soft Delete):当你通过 API 或 UI 点击删除时,Registry 只是删除了清单(Manifest)。这就像你在 Windows 上把文件拖进了回收站,文件其实还在磁盘上。
- 垃圾回收 (Garbage Collection):这是真正的物理删除。因为 Docker 的镜像是由很多“层(Layers)”组成的,且不同镜像会共享底层。Registry 必须通过扫描全盘,计算引用关系,确认某一层真的没有被任何镜像使用后,才敢真正删除它。
此外,官方默认禁用删除操作(REGISTRY_STORAGE_DELETE_ENABLED: false)主要基于以下考量:
- 层共享保护:防止误删被其他镜像依赖的基础层。
- 并发安全:防止在清理时,刚好有新镜像正在推送(Push)导致数据损坏。
- 不可变基础设施:发布的镜像应当被视为不可变的历史存档。
但在个人或小团队场景下,为了省钱(服务器磁盘贵啊!),我们需要它能够清理。
2. 编写 docker-compose.yml
我们将通过 Dokploy 部署三个服务:
- Registry:核心仓库服务。
- Auto-Auth:一个辅助容器,启动时生成账号密码文件,然后自动退出。
- Registry UI:一个轻量级的 Web 界面,方便我们查看和删除镜像。
在 Dokploy 的 Compose 选项卡中,填入以下配置:
version: '3.8'
# 定义数据卷,持久化存储镜像和认证信息
volumes:
registry-data:
registry-auth-data:
# 使用 Dokploy 预置的外部网络
networks:
dokploy-network:
external: true
services:
# 1. 认证辅助服务:生成账号 admin / 密码 123456
# 启动一次生成文件后就会退出,这是正常的
auto-setup-auth:
image: httpd:alpine
command: /bin/sh -c "htpasswd -Bbn admin 123456 > /auth/htpasswd"
volumes:
- registry-auth-data:/auth
# 2. 核心 Registry 服务
registry:
image: registry:2
container_name: my-private-registry
restart: always
networks:
- dokploy-network
# 映射端口方便本地调试,生产环境主要靠 Traefik 转发
ports:
- "5000:5000"
environment:
# 认证配置
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
# 反向代理配置
REGISTRY_HTTP_HEADERS_X_FORWARDED_FOR: true
REGISTRY_HTTP_HEADERS_X_FORWARDED_PROTO: https
# 【关键配置】开启删除权限
REGISTRY_STORAGE_DELETE_ENABLED: "true"
# 【跨域配置】允许 UI 界面通过浏览器调用 API
REGISTRY_HTTP_HEADERS_ACCESS_CONTROL_ALLOW_ORIGIN: '[https://registry-ui.zata.cafe]'
REGISTRY_HTTP_HEADERS_ACCESS_CONTROL_ALLOW_METHODS: '[HEAD,GET,OPTIONS,DELETE]'
REGISTRY_HTTP_HEADERS_ACCESS_CONTROL_ALLOW_HEADERS: '[Authorization,Accept,Cache-Control]'
REGISTRY_HTTP_HEADERS_ACCESS_CONTROL_EXPOSE_HEADERS: '[Docker-Content-Digest]'
volumes:
- registry-data:/var/lib/registry
- registry-auth-data:/auth
depends_on:
auto-setup-auth:
condition: service_completed_successfully
# Traefik 标签:配置域名和 HTTPS
labels:
- "traefik.enable=true"
- "traefik.http.routers.registry.rule=Host(`registry.zata.cafe`)"
- "traefik.http.routers.registry.entrypoints=websecure"
- "traefik.http.routers.registry.tls.certresolver=letsencrypt"
- "traefik.http.services.registry.loadbalancer.server.port=5000"
# 取消请求体大小限制,防止 Push 大镜像失败
- "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=0"
# 3. Web UI 管理界面
registry-ui:
image: joxit/docker-registry-ui:latest
container_name: registry-ui
restart: always
networks:
- dokploy-network
environment:
# 删除 REGISTRY_URL,改用下面这两个变量:
- NGINX_PROXY_PASS_URL=http://my-private-registry:5000
- SINGLE_REGISTRY=true
- DELETE_IMAGES=true
- REGISTRY_TITLE=Zata Registry
labels:
- "traefik.enable=true"
- "traefik.http.routers.registry-ui.rule=Host(`registry-ui.zata.cafe`)"
- "traefik.http.routers.registry-ui.entrypoints=websecure"
- "traefik.http.routers.registry-ui.tls.certresolver=letsencrypt"
- "traefik.http.services.registry-ui.loadbalancer.server.port=80"
注意:请将代码中的
zata.cafe替换为你自己的域名,并确保 DNS 解析已指向你的服务器。
点击 Deploy 部署应用。
3. 实现自动化清理 (GC)
部署完成后,你现在可以通过 registry-ui.zata.cafe 访问界面,并手动删除不需要的镜像 Tag。但这只是“软删除”。为了释放磁盘空间,我们需要在宿主机上配置定时任务。
步骤 1:创建清理脚本
SSH 登录到你的服务器,创建一个脚本文件:
nano /root/clean_registry.sh
写入以下内容:
#!/bin/bash
echo "Starting Registry Garbage Collection: $(date)"
# 调用容器内的 registry 工具执行垃圾回收
# -m 参数表示同时删除不再被引用的 manifest(清单)
docker exec my-private-registry /bin/registry garbage-collect /etc/docker/registry/config.yml -m
echo "Garbage Collection Finished: $(date)"
保存并退出(Ctrl+O, Enter, Ctrl+X),然后赋予执行权限:
chmod +x /root/clean_registry.sh
步骤 2:配置定时任务 (Crontab)
我们希望在每天凌晨(比如 4 点)没人使用的时候执行清理。
输入命令编辑定时任务:
crontab -e
在文件末尾添加一行:
# 每天凌晨 4:00 执行清理,并将日志输出到文件以便排查
0 4 * * * /root/clean_registry.sh >> /var/log/registry_gc.log 2>&1
4. 最终工作流
完成以上配置后,你的私有仓库工作流变成了这样:
- 日常开发:正常
docker push推送镜像。 - 手动维护:觉得某个镜像版本太老了?打开
registry-ui网页,点一下垃圾桶图标删除它(此时空间未释放)。 - 自动瘦身:不用管了,去睡觉吧。凌晨 4 点,服务器会自动运行 GC,扫描并彻底删除那些你标记为删除的文件块,第二天早上起来,磁盘空间就回来了。
总结
通过 Dokploy 配合 Traefik,我们不仅拥有了一个 HTTPS 安全的私有仓库,还加上了可视化管理界面。再配合简单的 Shell 脚本和 Crontab,解决了 Docker Registry 最令人头疼的磁盘占用问题。
Happy Coding! 🐳
实战指南:使用 Docker 和 Traefik 搭建私有镜像仓库(及踩坑记录)— 202602/23
在 DevOps 流程中,拥有一个私有的 Docker Registry(镜像仓库)是必不可少的。虽然 Docker Hub 很好用,但出于隐私、速度和成本的考虑,自建仓库往往是更好的选择。本文将手把手教你如何使用 Docker Compose 和 Traefik 反向代理搭建一个带 HTTPS 和基础认证的轻量级私有仓库,并重点分析部署过程中最容易遇到的 “Gateway Timeout” 和 “Connection Refused” 等网络问题。
为什么选择这个方案?
- 轻量级:基于官方
registry:2镜像,资源占用极小。 - 安全:通过 Traefik 自动管理 SSL 证书(Let’s Encrypt),并配置
htpasswd基础认证。 - 易维护:所有配置通过一个
docker-compose.yml文件管理。
准备工作
- 一台安装了 Docker 和 Docker Compose 的 Linux 服务器。
- 一个域名并解析到服务器 IP(例如:
registry.example.com)。 - Traefik 已经在运行中(如果你使用的是 Dokploy、Coolify 等面板,Traefik 通常是内置好的)。
核心配置:docker-compose.yml
为了方便演示,我们使用一个技巧:利用临时容器自动生成密码文件,避免手动安装 htpasswd 工具的麻烦。请创建一个目录 my-registry,并在其中新建 docker-compose.yml,内容如下:
version: '3.8'
# 定义数据卷,持久化存储镜像和认证信息
volumes:
registry-data:
registry-auth-data:
# 【重点】定义网络
# 这里必须使用 external: true,表示使用外部已经存在的 Traefik 网络
# 如果你用的是 Dokploy,网络名通常是 dokploy-network
# 如果你是自己部署的 Traefik,通常叫 traefik_public 或 proxy
networks:
dokploy-network:
external: true
services:
# 1. 辅助服务:自动生成账号密码
# 启动后会生成密码文件并存入卷中,然后自动退出
auto-setup-auth:
image: httpd:alpine
# 账号: admin, 密码: 123456 (生产环境请修改!)
command: /bin/sh -c "htpasswd -Bbn admin 123456 > /auth/htpasswd"
volumes:
- registry-auth-data:/auth
# 2. 核心仓库服务
registry:
image: registry:2
container_name: my-private-registry
restart: always
# 【关键点 1】加入 Traefik 所在的网络
networks:
- dokploy-network
# 端口映射(可选,仅用于本地调试,生产环境可注释掉)
ports:
- "5000:5000"
environment:
# 启用认证
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
# 告诉 Registry 它运行在 HTTPS 反向代理后面
REGISTRY_HTTP_HEADERS_X_FORWARDED_FOR: true
REGISTRY_HTTP_HEADERS_X_FORWARDED_PROTO: https
volumes:
- registry-data:/var/lib/registry
- registry-auth-data:/auth
depends_on:
auto-setup-auth:
condition: service_completed_successfully
# 【关键点 2】Traefik 标签配置
labels:
- "traefik.enable=true"
# 修改为你的实际域名
- "traefik.http.routers.registry.rule=Host(`registry.example.com`)"
# 指定入口点为 HTTPS
- "traefik.http.routers.registry.entrypoints=websecure"
# 启用 TLS (自动申请证书)
- "traefik.http.routers.registry.tls.certresolver=letsencrypt"
# 【至关重要】告诉 Traefik 容器内部端口是 5000
- "traefik.http.services.registry.loadbalancer.server.port=5000"
# 取消请求体大小限制(防止 docker push 大镜像失败)
- "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=0"
配置完成后,使用 docker-compose up -d 命令启动服务。
常见问题排查(Troubleshooting)
在部署过程中,你可能会遇到以下几种错误,请根据现象进行排查:
1. 504 Gateway Timeout (网关超时)

- 现象:浏览器访问域名/v2/,加载很久后显示
504 Gateway Timeout。 - 原因:这说明 Traefik 接到了请求,但是无法连接到后端的 Registry 容器。这通常是因为 Traefik 和 Registry 不在同一个 Docker 网络 中。Traefik 就像大楼的前台,它想转接电话给 Registry,但发现线路不通。
- 解决方案:检查
docker-compose.yml中的networks部分。必须确保 Registry 加入了 Traefik 所在的那个网络(通过docker network ls查看网络名称)。
2. Context Deadline Exceeded (连接超时)
- 现象:在使用 Dokploy 或命令行登录时,提示
Client.Timeout exceeded while awaiting headers。 - 原因:这通常是网络层面的物理隔绝。你的请求根本没有到达服务器,被防火墙拦在了外面。
- 解决方案:
- 检查云服务商(AWS/阿里云/腾讯云)的安全组,必须开放 TCP 443 和 80 端口。
- 检查服务器内部防火墙(如
ufw或iptables)。
3. 登录失败或 404 Not Found
- 现象:访问域名能通,但是 docker login 总是失败,或者页面显示 404。
- 原因:可能是 Traefik 把流量转发到了错误的端口。Registry 默认监听 5000,而 Traefik 默认转发到 80。
- 解决方案:确保
labels中包含指定端口的配置:- "traefik.http.services.registry.loadbalancer.server.port=5000"。
4. 推送镜像失败 (Blob Upload Unknown)
- 现象:登录成功,但在
docker push大文件时失败并重试。 - 原因:Traefik 或 Nginx 默认会限制上传文件的大小。
- 解决方案:添加中间件配置取消限制:
- "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=0"。
验证与使用
部署成功后,建议按以下步骤进行测试:
- 浏览器访问:打开
https://registry.example.com/v2/,应该弹出登录框,输入admin/123456,页面显示{}即为成功。 - Docker 登录:在终端执行
docker login registry.example.com,输入配置的用户名和密码。 - 推送镜像:
- 给本地镜像打标签:
docker tag my-image:latest registry.example.com/my-image:latest - 推送至仓库:
docker push registry.example.com/my-image:latest
- 给本地镜像打标签:
总结
搭建私有仓库并不难,难点通常在于网络配置。记住一句话:“Traefik 是网关,防火墙是门卫,网络是内部通道。” 只有这三者都配置正确,你的镜像仓库才能顺畅运行。希望这篇指南能帮你避开常见的陷阱。
