为多个 Docker Compose 项目编写自动更新脚本:打造轻量、可控的无人值守运维流

导语:随着 VPS 上部署的服务越来越多,WordPress、Nginx Proxy Manager、Redis、探针、爬虫……每个应用都分布在不同的目录中。如果每次都要手动 cd 进每个文件夹执行 pull 和 up -d,不仅耗时耗力,而且容易遗忘。本文将手把手带大家编写一个优雅健壮的 Shell 脚本,实现全自动批量更新、孤儿镜像自动清理、日志持久化追踪,并通过 Crontab 实现每天凌晨的无感无人值守更新。


一、 为什么不用第三方工具(如 Watchtower)?

在 Docker 自动化更新领域,很多人第一时间会想到 Watchtower。Watchtower 确实开箱即用,但在实际长期运行中,手写轻量级 Shell 脚本往往具备不可替代的优势:

对比维度 Watchtower 容器 自建 Shell 脚本(本篇方案)
内存与系统开销 额外常驻一个后台容器(占用 30M~50M 内存) 0 常驻开销,仅在触发定时任务时唤醒执行数秒
可控性与灵活性 规则配置相对黑盒,网络抖动易误判 透明可控,想更新哪些目录、何时更新一目了然
垃圾镜像清理 默认可能堆积无用旧镜像 脚本内置自动清理,彻底告别硬盘被塞满的隐患
依赖程度 依赖 Docker API 与第三方镜像安全性 纯原生 Bash,零外部依赖,安全纯粹

对于追求极致轻量、服务器配置有限或喜欢掌控细节的极客来说,一个几十行的精巧 Shell 脚本才是最惬意的选择。


二、 现状梳理:分散的多项目目录

假设我们在服务器主目录(如 /root)下维护着多个独立的 Docker Compose 项目,典型的结构如下:

/root
├── data/docker_data/nodeseek/
│   └── docker-compose.yml       # RSS 订阅抓取服务
├── komari/
│   └── docker-compose.yml       # 监控探针服务端
├── npm/
│   └── docker-compose.yml       # Nginx Proxy Manager 反代网关
└── wordpress/
    └── docker-compose.yml       # 个人博客系统全家桶

我们的核心目标是:脚本能够自动遍历这些离散的目录,按顺序拉取最新镜像、重启更新容器,并在完成后自动扫除历史废弃镜像,将全程细节忠实记录到日志中。


三、 脚本编写:生产级增强版自动更新脚本

在服务器上创建脚本文件:

vi /root/update_all_compose.sh

写入以下优化后的增强版脚本代码。代码中包含了详细的中文注释、错误跳过逻辑、执行时间统计以及至关重要的 镜像清理机制:

#!/bin/bash

###############################################################
# 多 Docker Compose 项目自动更新维护脚本
# 特性:批量更新、网络容错、废弃镜像自动回收、耗时统计、全量日志
###############################################################

# 1. 定义待维护的 compose 文件绝对路径列表(根据实际情况增减)
compose_files=(
    "/root/data/docker_data/nodeseek/docker-compose.yml"
    "/root/komari/docker-compose.yml"
    "/root/npm/docker-compose.yml"
    "/root/wordpress/docker-compose.yml"
)

# 2. 定义运行日志文件路径
log_file="/root/docker_auto_update.log"

# 记录开始时间戳
start_time=$(date +%s)
echo "==========================================================" >> "$log_file"
echo "[INFO] 自动更新任务启动时间: $(date '+%Y-%m-%d %H:%M:%S')" >> "$log_file"
echo "==========================================================" >> "$log_file"

# 兼容检测:优先使用现代 docker compose,旧环境回退到 docker-compose
if docker compose version >/dev/null 2>&1; then
    DOCKER_COMPOSE_CMD="docker compose"
else
    DOCKER_COMPOSE_CMD="docker-compose"
fi

# 3. 循环遍历每个项目执行维护
for file in "${compose_files[@]}"; do
    echo "" >> "$log_file"
    echo "--------------------------------------------------------" >> "$log_file"
    echo "[TASK] 正在检查项目: $file" >> "$log_file"

    # 文件存在性防御检查
    if [ ! -f "$file" ]; then
        echo "[WARN] 文件不存在,跳过该项目: $file" >> "$log_file"
        continue
    fi

    # 获取项目父级目录
    compose_dir=$(dirname "$file")

    # 切换到工作目录
    cd "$compose_dir" || {
        echo "[ERROR] 无法进入目标目录: $compose_dir" >> "$log_file"
        continue
    }

    # 步骤 A:拉取最新镜像
    echo "[STEP 1] 正在拉取最新镜像..." >> "$log_file"
    $DOCKER_COMPOSE_CMD -f "$file" pull >> "$log_file" 2>&1

    # 步骤 B:重新构建并拉起更新容器
    echo "[STEP 2] 正在应用配置并平滑重启..." >> "$log_file"
    $DOCKER_COMPOSE_CMD -f "$file" up -d >> "$log_file" 2>&1

    echo "[SUCCESS] 项目 $compose_dir 更新处理完毕" >> "$log_file"
done

# 4. 关键清理步骤:自动清理标签为 none 的废弃悬空镜像 (dangling images)
echo "" >> "$log_file"
echo "[CLEANUP] 正在自动清理过期悬空镜像 (dangling images)..." >> "$log_file"
docker image prune -f >> "$log_file" 2>&1

# 5. 结束与耗时汇总
end_time=$(date +%s)
cost_time=$((end_time - start_time))

echo "" >> "$log_file"
echo "==========================================================" >> "$log_file"
echo "[INFO] 全部更新任务顺利完成!" >> "$log_file"
echo "[INFO] 完成时间: $(date '+%Y-%m-%d %H:%M:%S') | 总计耗时: ${cost_time} 秒" >> "$log_file"
echo "==========================================================" >> "$log_file"
echo "" >> "$log_file"

保存并退出编辑器。

🌟 核心改进亮点:

  • 加了 docker image prune -f:Docker 每次更新镜像后,旧镜像的 tag 会变成 none(悬空镜像 / dangling images)。如果不清理,长期运行会吞掉几十 GB 磁盘空间。加入这行自动清理,能确保服务器硬盘始终清爽!
  • 命令兼容性自适应:自动在现代 docker compose 与传统 docker-compose 之间无缝切换。

四、 赋予权限与手动试跑

在正式交给定时任务前,我们必须手动执行一次,验证脚本逻辑与日志输出是否健康。

1. 赋予脚本执行权限

chmod +x /root/update_all_compose.sh

2. 手动执行测试

/bin/bash /root/update_all_compose.sh

3. 查看实时执行日志

运行中或执行完毕后,通过 tail 命令检查生成日志:

tail -n 30 /root/docker_auto_update.log

如果看到类似以下整洁规范的日志输出,说明脚本已经完全打通:

==========================================================
[INFO] 自动更新任务启动时间: 2026-10-19 04:00:00
==========================================================

--------------------------------------------------------
[TASK] 正在检查项目: /root/wordpress/docker-compose.yml
[STEP 1] 正在拉取最新镜像...
[STEP 2] 正在应用配置并平滑重启...
[SUCCESS] 项目 /root/wordpress 更新处理完毕

[CLEANUP] 正在自动清理过期悬空镜像 (dangling images)...
Total reclaimed space: 1.24GB

==========================================================
[INFO] 全部更新任务顺利完成!
[INFO] 完成时间: 2026-10-19 04:01:25 | 总计耗时: 85 秒
==========================================================

五、 配置系统定时任务(Crontab 无人值守)

验证无误后,我们将其交由 Linux 自带的 Crontab 调度引擎,设定在系统访问最稀疏的凌晨时段静默执行。

1. 编辑当前用户的定时任务

crontab -e

💡 小贴士:如果是首次运行 crontab -e,系统可能会让你选择编辑器,输入数字选择熟悉的 nano 或 vim 即可。

2. 追加定时任务表达式

在文件末尾新起一行,粘贴以下内容:

0 4 * * * /bin/bash /root/update_all_compose.sh >/dev/null 2>&1

表达式深度解析:

  • *`0 4 `:代表 每天凌晨 04:00** 准时拉起任务。这个时段网站访问量极低,即使重启服务也不会影响日常用户体验。
  • /bin/bash /root/update_all_compose.sh:在 Crontab 环境中,环境变量往往极度精简,因此必须使用绝对路径来指定解释器和脚本,规避“找不到命令”的经典隐患。
  • >/dev/null 2>&1:由于脚本内部已经将所有执行细节完整追加写入到了 /root/docker_auto_update.log 中,因此直接屏蔽系统默认的内部邮件输出,避免无谓的系统提示。

六、 总结与延伸

通过这套精心设计的百行脚本,我们用最轻量的原生方式实现了:

  1. 统一收拢管理:多目录多项目一键调度。
  2. 磁盘自我净化:杜绝废弃旧镜像蚕食服务器有限存储。
  3. 运维状态可溯:每次更新耗时、拉取结果白纸黑字留存日志。

从此,服务器上的所有核心服务每天清晨都会披上最新的安全补丁与功能更新,而你只需泡一杯咖啡,尽情享受稳定而省心的云端服务 ☕️。

上一篇 WordPress 建站实战 2️⃣:基于 Docker + Nginx Proxy Manager 打造解耦型高性能博客
下一篇 为了救活晚高峰掉线的探针,我顺手把监控和博客都干趴了……
目录
keiomn

keiomn管理员

这个人很懒,什么都没有留下。

本月创作热力图

少
多