乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

Open Knowledge Framework 一套让智能体越用越好用的成长框架

发表于 2026-05-11

前言

知识不等于行为,行为不能靠自觉,要靠机制。

一个真实的问题

我用 Coze 平台搭了一堆智能体帮我干活——写代码、混音乐、写网文、做视频、运营社媒……一开始很顺畅,但很快发现了一个致命问题:

智能体会失忆。

今天踩的坑,明天换个会话又踩一遍。上周验证过的方案,这周重新摸索。一个角色积累的经验,另一个角色完全不知道。

更头疼的是多角色协作:系统开发者改了个配置,ECS 运维不知道,部署时崩了;运营发现平台规则变了,没通知到创作者,内容被打回。每个角色都在”独立聪明”,但没有”一起变聪明”。

我需要的不是一个更聪明的 Prompt,而是一套机制——让智能体的知识能沉淀、能流通、能自动触发行为。

这就是 Open Knowledge Framework 的起点。

它是什么

Open Knowledge Framework 是一套面向 AI 智能体的成长操作系统(开源、MIT 协议)。

它不是工具箱,不是模板库,而是一套让智能体从”能用”→”好用”→”自己变更好用”的成长机制。

核心理念用一句话说:知识不等于行为,行为不能靠自觉,要靠机制。机制用步骤来实现,步骤要读写闭环。

五个设计支柱

支柱 含义 为什么重要
无限扩展 新功能 = 注册扩展,架构本身永远不需要改 今天 3 个角色,明天 30 个,体系不能重写
高效少错 机制替代自觉,步骤替代记忆 智能体会失忆、会偷懒、会自作主张
省 Token 每句话都花钱,少废话多干活 不做无用搜索、不重复操作、不空转等待
平台无关 抽象掉平台差异,核心逻辑可迁移 扣子→飞书→Hermes→机器人,体系代码不用改
实用耐用 规则写在文件里,不靠人记住 踩过的坑自动升级成规则,重犯 = 体系失职

它解决什么问题

问题 1:智能体会失忆

现象:每次对话都像第一次见面,之前踩过的坑、积累的经验全部归零。

解法:三层记忆体系

  • 即时层:自动加载到上下文——身份定义、用户画像、当前状态、工具经验、敏感凭证
  • 近中期层:按需加载——项目进度快照、重要决策记录、待办事项
  • 长期层:语义检索——历史对话和文件内容的 RAG 搜索

关键设计:记忆有分层上限,不是所有东西都往一个文件里塞。超过上限的内容自动沉淀到详细文件,索引层只保留指针。

问题 2:知识不等于行为

现象:规则写在文档里 ≠ 智能体真的会遵守。踩坑记录在知识库里 ≠ 下次不会重犯。

解法:执行门机制

每个角色都有专属的”执行门”——强制性的步骤化流程:

1
① 查后定方案 → ② 执行 → ③ 验证 → ④ 反哺 → ⑤ 交付

每一步都有明确的检查项,跳步 = 不合格。关键的是第 ④ 步”反哺”——做完任务必须把经验写回知识体系,否则任务不算完成。

问题 3:踩过的坑反复踩

现象:同一个错误,换个会话、换个角色,又犯一遍。

解法:知识自动升级机制

1
2
3
踩 1 次 → 记入 knowledge(被动防御,搜索才能找到)
踩 2 次 → 升级为 hot-rules(每次加载必读,主动防御)
系统性风险 → 升级为铁律或脚本保护(物理不可绕过)

典型案例:图片生成任务传了 count=5,一次烧了 5 万积分。这个教训从 knowledge 升级为铁律后,所有相关任务都自动继承这条规则,再也没犯过。

问题 4:多角色协作断层

现象:各角色各自为战,A 改了配置 B 不知道,C 的需求 D 没收到。

解法:三层单向关联 + 交接台

1
项目 → 角色 → 技能(单向关联,消费方找提供方)
  • 项目 INDEX.md 声明关联了哪些角色
  • 角色 RULES.md 声明需要哪些技能
  • 交接台是所有角色启动时的第一站,防止遗漏和断链

体系架构

文件即框架

整个框架完全由 Markdown 文件和 Shell 脚本构成,不依赖任何特定平台或数据库。知识在文件中,平台只是运行时。

1
2
3
4
5
6
7
宪法层(改一次全局生效)    共享知识/项目规范/
蓝图层(愿景 + 哲学) 共享知识/蓝图.md
角色层(执行者) 角色/[角色名]/
技能层(可复用能力) 技能/[技能名]/
项目层(产出物) 项目/[项目名]/
运行时(工单流转) 交接台/
自动化(引擎脚本) scripts/

Extension Registry 模式

这是架构的核心模式:新功能 = 注册扩展,架构本身永远不需要改。

1
2
3
4
添加新角色 → 不改框架,只创建 角色/新角色/
添加新技能 → 不改框架,只创建 技能/新技能/
添加新项目 → 不改框架,只创建 项目/新项目/
添加新工具 → 不改框架,只注册新扩展

这不是一句口号,是设计约束。无论体系从 3 个角色扩展到 30 个还是 300 个,目录结构、协作规范、执行流程都不需要改。

核心机制详解

1. 五步门——所有任务的标准流程

每个角色执行任务时必须走完五步门,跳步 = 不合格:

步骤 动作 防什么
① 查后定方案 先搜索现有知识和踩坑记录,再定方案 防盲干、防重复造轮子
② 执行 按方案执行,改前备份 防发散、防覆盖
③ 验证 跑测试/自检/扫描 防”自以为写对了”
④ 反哺 写回 INDEX / knowledge / hot-rules 防白干、防失忆
⑤ 交付 推送代码,闭环确认 防忘推、防半成品

2. 知识分层——经验跟着人走

做完任务后,经验不是随便一存,而是先判断归属:

归属 写到哪里 判断标准
项目特有 项目 docs/knowledge/ 换个项目没用了
角色通用 角色 knowledge/ 换个项目还有用
全局共享 共享知识/ 换个角色还能用

3. 自动化脚本——让机器干机器的活

框架内置 40+ 个自动化脚本,覆盖初始化、推送、同步、归档、索引重建、协作引擎等:

脚本 用途
init.sh 一键配置 git 用户/token/remote
push.sh 安全推送(commit + pull + push + 检查)
act.sh 意图检索 + 前科必显(1 秒索引加速)
rebuild-index.sh 重建搜索索引
archive.sh 文件归档到回收站
create-repo.sh 创建仓库 + GitHub/Gitee/Gitea 三推
auto-collab.sh 自动化协作引擎(匀速/加速/并行/聚焦)

4. 多平台容灾——单平台故障不影响安全

所有仓库配置 GitHub + Gitee + Gitea 三推送,一条 git push 推三个平台,git pull 只走 GitHub。

Gitee 和 Gitea 全部私有,绝不在公开仓库暴露任何内部信息。

我的实际使用数据

这套框架不是理论设计,是我在日常使用中一步步迭代出来的:

维度 数据
角色数量 25 个(系统开发者、ECS 运维、混音母带工程师、网文写手、动画导演……)
项目数量 22 个(OpenDAW、OpenLink、动漫制作、网文创作……)
技能数量 34 个
Markdown 文件 1867 个
自动化脚本 40+ 个
迭代版本 v30(从 v1 到 v30,每个版本都是踩坑换来的)

快速上手

3 分钟启动

1
2
3
4
5
6
7
8
9
10
# 1. Fork 框架
# GitHub 上 Fork https://github.com/youbanzhishi/open-knowledge-framework
git clone https://github.com/<你的用户名>/open-knowledge-framework.git
cd open-knowledge-framework

# 2. 一键初始化
bash scripts/init.sh <平台名>

# 3. 让你的智能体读 入口.md
# 智能体会自动走流程、建目录、开始工作

定制你的体系

1
2
3
4
5
6
7
8
9
10
11
# 定义智能体身份
vim 基础设定/SOUL.md

# 添加角色
cp -r 角色/模板 角色/你的角色名

# 添加项目
cp -r 项目/模板 项目/你的项目名

# 添加技能
cp -r 技能/模板 技能/你的技能名

适合谁

✅ 适合:

  • 用多个智能体协作开发项目的团队或个人
  • 想让智能体”越用越聪明”而不是”每次从零开始”的人
  • 需要跨项目、跨平台知识流通的场景
  • 希望用一套统一框架管理所有智能体的知识工作者

❌ 不适合:

  • 只用一个智能体做一个简单任务
  • 不需要知识积累的一次性工作

开源与社区

  • 仓库地址:github.com/youbanzhishi/open-knowledge-framework
  • 许可证:MIT(自由使用、修改、分发)
  • 协作模式:Fork → 增强 → PR 回公共仓库

欢迎 PR 回来的内容

  • ✅ 通用角色/技能/项目模板改进
  • ✅ 脚本 bug 修复或功能增强
  • ✅ 新的通用设计模式
  • ✅ 规范文档改进

留在你私有仓库的内容

  • ❌ 你的私有项目内容和个人信息
  • ❌ 你的凭据和密钥
  • ❌ 与具体业务相关的定制

写在最后

这套框架的起点不是什么宏大的技术愿景,而是一个朴素的诉求——我不想再重复踩同一个坑了。

从最初的几条规则,到五步门,到执行门分层,到知识自动升级机制,到多角色协作网络……每一步迭代都是被真实的坑逼出来的。

今天它还在持续进化。每一次协作中的失误,都会变成体系的一部分。每一次成功的经验,都会流通到更多角色和项目。

知识不等于行为,行为不能靠自觉,要靠机制。

如果你也在用智能体干活,如果你也觉得”每次从零开始”很痛苦,试试这套框架。


Open Knowledge Framework — 让智能体从”能用”到”好用”到”自己变更好用”。

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

开发一套AI智能体协作知识体系

发表于 2026-05-10

前言

最开始使用智能体也就是从OpenClaw开始, 通过技能的封装来实现经验的复用, 面对简单的流程性问题, 技能完全可以足够应付我们的需求, 同时也方便用户的交流.

但是遇到复杂且庞大的项目, 技能就显得有些鸡肋了

另外就是, 智能体经常不守规矩, 乱删文件, 喜欢自作主张

还有就是智能体协作的问题, 目前主流的方式是通过多agent实现, 每个agent对接一个独立的飞书机器人, 然后在同一个群中进行协作, 挺好的一个方案, 然而直到hermes的出现, 我突然意识到一个问题, 未来会有更多新的智能体平台诞生, 追着新鲜事物跑并不是一个明智的选择, 我们需要跳出这个体系

为了解决自身的一些需求, 于是决定借助AI自己手搓一个智能体知识体系

该体系的目的不是让智能体变聪明, 而是让他们更守规矩, 按照我们预设的流程和规范来做事, 同时可以实现自我成长, 进入越用越好用的良性循环状态

架构设计

模拟真实世界的做事规则, 一个项目可以由多个角色共同参与完成, 一个角色在职责范围内具备多个技能, 当我们在做一个项目的时候, 会不断积累项目经验, 有一些经验归属于角色, 有一些经验独属于项目, 可复用的项目经验提炼到公共知识库, 可以提升下一个项目的执行效率, 所以知识体系架构如下:

  • 公共知识库
  • 项目
  • 角色
  • 技能

项目可以关联多个角色, 角色可以关联多个技能, 其角色是最为核心的部分, 整个体系可以没有项目没有技能, 但不能缺失角色, 也就是现实中的人

知识体系详解

由于该体系是通过逐步迭代完善而来, 优点比较多, 我一时半会儿也很难说的全面, 索性直接让智能体来帮我生成了一般详细文档, 并单开一篇独立博文介绍, 详情可参见:《Open Knowledge Framework 一套让智能体越用越好用的成长框架》

知识体系的使用

我自己用了一段时间, 觉得很好用, 因此把核心部分抽出来分享给大家使用, 大家可以基于该框架自行拓展

项目已开源:

1
https://github.com/youbanzhishi/open-knowledge-framework

当然了, 我还开发一个短链系统, 如果你嫌麻烦, 想要直接上手使用, 那么只需要一条链接即可, 将下面这条链接发送给任意智能体, 无论是元宝还是豆包扣子, 都可以自动识别:

1
https://y6e.cn/we-ai

总结

今天OpenClaw, 明天Hermes, 后天可能是别的, 无论怎么变我们的知识体系都可以无缝过渡使用, 足以应对未来AI变化

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

如何配置 OpenClaw 使用自定义模型

发表于 2026-04-22

前言

网上撸了很多免费的大模型,比如元宝、阿里云、飞书、天翼云等等,由于都是限量供应,比如元宝的和阿里云的 Api 每天会提供一定的额度,所以不得不在多个模型中来回切换。
有些厂商的定版制openclaw 除了飞书外,其他的比如阿里云的 JVS Claw在模型配置面板中只支持部分知名模型供应商,无法随意配置自定义模型,而元宝压根就不支持第三方模型的配置,所以如果我们想要自定义,那就只能通过指令配置的形式了。
指令的执行需要依靠终端窗口,但是没关系,只能你能和龙虾对上话,哪怕厂商不暴露终端给你也一样能配置,让龙虾帮你配就好了, 这还不好简单😉

那么接下来,就跟大家分享一下如何配置自定义模型

第一步:增加自定义模型配置

首先,您需要在 OpenClaw 的配置中注册您的模型提供商和模型信息。

1. 清理旧配置(如存在)

如果之前配置过同名模型,建议先清理旧配置以确保环境干净。

1
openclaw config unset models.providers.<your_provider_name>

提示:请将上述命令中的 <your_provider_name> 替换为您的自定义标识,例如 yuanbao。

2. 激活模型配置模式

确保 OpenClaw 的模型模式设置为 merge,以允许合并新的模型配置。

1
openclaw config set models.mode merge

3. 初始化模型提供商配置结构

确保 models.providers 配置项存在且为一个空对象,为后续注入配置做好准备。

1
openclaw config set models.providers '{}'

4. 注入自定义模型配置

使用 --json 参数,以正确的 JSON 格式注入您的模型详细信息。这是最关键的一步。

1
2
3
4
5
6
7
8
9
10
11
openclaw config set 'models.providers.<your_provider_name>' --json '{
"baseUrl": "这里填你模型的 baseUrl",
"apiKey": "这里填你模型的apiKey",
"api": "openai-completions",
"models": [
{
"id": "这里填模型id",
"name": "这里填模型名称"
}
]
}'

重要参数说明:

  • baseUrl:模型服务实际的 API 端点地址。
  • apiKey: 模型 API 密钥。
  • api:指定适配的 API 协议,openai-completions 表示兼容 OpenAI 的补全接口。
  • models.id:模型在服务端的唯一标识符。
  • models.name:为您模型设置的展示名称。

执行效果:成功执行后,您的配置文件openclaw.json 中会新增类似以下的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"models": {
"providers": {
"<your_provider_name>": {
"baseUrl": "<your_model_api_base_url>",
"apiKey": "<your_api_key>",
"api": "openai-completions",
"models": [
{
"id": "<your_model_id>",
"name": "<your_model_display_name>"
}
]
}
}
}
}

第二步:设置默认模型

注册模型后,需要将其指定为 OpenClaw 智能体(Agents)使用的默认主模型。

在容器内执行以下命令,将您的模型设置为全局默认模型:

1
openclaw config set agents.defaults.model.primary <your_provider_name>/<your_model_id>

关键点解析:

  • 配置路径:agents.defaults.model.primary 是 OpenClaw 框架中用于设置主模型的标准配置路径。
  • 格式规范:值必须采用 provider/modelId 的格式。

执行效果:此命令将在配置文件中新增如下结构,使系统默认调用您指定的模型:

1
2
3
4
5
6
7
8
9
{
"agents": {
"defaults": {
"model": {
"primary": "<your_provider_name>/<your_model_id>"
}
}
}
}

第三步:重启网关并验证

配置修改完成后,需要重启相关服务以使新配置生效。

1. 重启 OpenClaw 网关

执行以下命令重启网关服务,加载最新的模型配置。

1
openclaw gateway restart

2. 验证配置状态

重启完成后,使用以下命令查看当前系统中已就绪且生效的默认模型,确认配置是否成功。

1
openclaw models status

当命令输出中显示您配置的模型(如 <your_provider_name>/<your_model_id>)为可用状态时,即表示自定义模型配置并设置默认成功。

总结

总共分为 注册模型、设为默认、重启生效 三个步骤,简单即可完成 OpenClaw 对自定义模型的集成,赶紧去尝试一下吧😁

分部配置方案

有时候我们可能只需要修改某个模型的密钥或者名称,那么可以使用单独的指令会更加方便

具体模板可以参考如下:

第一步:清理配置

1
openclaw config unset models.模型别名

第二步:设置运行模式

1
openclaw config set models.mode "merge"

第三步:配置基础信息

1
2
3
4
5
6
7
8
# 设置 API 端点地址
openclaw config set models.providers.模型别名.baseUrl "API基础URL"

# 设置 API 密钥
openclaw config set models.providers.模型别名.apiKey "API密钥"

# 设置 API 类型
openclaw config set models.providers.模型别名.api "API类型"

第四步:配置模型列表

1
2
3
4
5
6
7
8
9
10
# 1. 先清空 models 配置
openclaw config unset models.providers.模型别名.models

# 2. 添加第一个模型
openclaw config set models.providers.模型别名.models[0].id "第一个模型ID"
openclaw config set models.providers.模型别名.models[0].name "第一个模型显示名称"

# 3. 如需添加更多模型(可选)
openclaw config set models.providers.模型别名.models[1].id "第二个模型ID"
openclaw config set models.providers.模型别名.models[1].name "第二个模型显示名称"

元宝配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 清理配置
openclaw config unset models.yuanbao

# 2. 设置运行模式
openclaw config set models.mode "merge"

# 3. 配置基础信息
openclaw config set models.providers.yuanbao.baseUrl "https://bot.yuanbao.tencent.com/api/bot"
openclaw config set models.providers.yuanbao.apiKey "675628accfc4963a4a9f9c42fe98014cd29d6522717f5a79976165630c55d1bd2a8cfe4bcf821451c0455ed302eeb8d97d5fb52e8c0dd3ba07519b08d90c17845c710b4ffda5fbe7e4383d51fb64"
openclaw config set models.providers.yuanbao.api "openai-completions"

# 4. 配置模型
openclaw config unset models.providers.yuanbao.models
openclaw config set models.providers.yuanbao.models[0].id "openclaw_yuanbao_robot_model"
openclaw config set models.providers.yuanbao.models[0].name "腾讯元宝"

多模型配置示例

1
2
3
4
5
6
7
8
9
10
# 清空后重新设置多个模型
openclaw config unset models.providers.yuanbao.models

# 添加第一个模型
openclaw config set models.providers.yuanbao.models[0].id "openclaw_yuanbao_robot_model"
openclaw config set models.providers.yuanbao.models[0].name "腾讯元宝"

# 添加第二个模型
openclaw config set models.providers.yuanbao.models[1].id "yuanbao-pro"
openclaw config set models.providers.yuanbao.models[1].name "元宝Pro版"

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

从零散Docker容器一键生成 docker-compose 编排配置的自动化方案

发表于 2026-04-22

前言

许多开发者在早期使用Docker时,习惯通过手动执行docker run命令启动容器。随着项目复杂度增加,这种方式的问题逐渐暴露:配置难以复现、迁移成本高、团队协作困难。本文将重点介绍如何通过自动化工具,从现有容器中一键生成规范的docker-compose.yml配置,实现快速迁移和标准化部署。

核心自动化工具

1. docker-autocompose:命令行利器

docker-autocompose是一个专为逆向工程设计的命令行工具,能够从现有容器自动生成docker-compose.yml配置。

基本用法

1
2
3
4
# 分析指定容器并生成配置
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/red5d/docker-autocompose:latest \
wp-site1 wp-site2 mysql-db

保存到文件

1
2
3
4
# 将输出重定向到文件
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/red5d/docker-autocompose:latest \
wp-site1 wp-site2 mysql-db > docker-compose.yml

高级特性

  • 支持已停止的容器:工具通过读取Docker引擎的元数据工作,不要求容器处于运行状态
  • 保留所有配置:自动提取端口映射、环境变量、数据卷挂载、网络设置等
  • 多容器分析:可一次性分析多个相互关联的容器

2. 交互式工具:compose-generator

对于偏好图形界面的用户,compose-generator提供了交互式逆向生成体验。

通过Docker运行

1
2
3
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd):/cg/out \
ghcr.io/compose-generator/compose-generator:latest

工作流程

  1. 启动后选择”From running project”
  2. 在列表中选择要分析的容器
  3. 跟随向导完成配置生成
  4. 生成的docker-compose.yml将输出到当前目录

实战案例:WordPress站点自动化迁移

场景描述

现有三个运行中的容器:两个WordPress站点(wp-site1、wp-site2)共用一个MySQL数据库(mysql-db)。

自动化步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 第一步:一键生成编排配置
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/red5d/docker-autocompose:latest \
wp-site1 wp-site2 mysql-db > docker-compose.yml

# 第二步:自动生成环境变量模板
cat > .env << 'EOF'
# 自动生成的配置模板
# 请替换为实际的安全密码
DB_PASSWORD=your_secure_password_here
MYSQL_ROOT_PASSWORD=your_root_password_here
EOF

# 第三步:验证生成的配置
docker-compose config

生成结果示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 自动生成的docker-compose.yml
version: '3.8'
services:
wp-site1:
image: wordpress:latest
ports:
- "8081:80"
environment:
WORDPRESS_DB_HOST: mysql-db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_DB_NAME: wordpress_site1
volumes:
- wp_site1_data:/var/www/html
networks:
- wp_network
depends_on:
- mysql-db

wp-site2:
image: wordpress:latest
# ... 类似配置
mysql-db:
image: mysql:latest
# ... 数据库配置

进阶:自动化版本号检测与替换

自动化工具生成的配置中,镜像标签通常是latest。为了确保环境一致性,建议固化版本号。

自动化版本检测脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# 自动检测并替换latest标签为具体版本
for container in wp-site1 wp-site2 mysql-db; do
image_name=$(docker inspect -f '{{.Config.Image}}' $container)
if [[ "$image_name" == *:latest ]]; then
actual_version=$(docker image inspect $image_name | grep -i version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+|[0-9]+\.[0-9]+' | head -1)
if [ -n "$actual_version" ]; then
# 在docker-compose.yml中替换版本
base_image=${image_name%:*}
sed -i "s|image: $base_image:latest|image: $base_image:$actual_version|g" docker-compose.yml
fi
fi
done

Dockerfile的自动化逆向生成

对于需要重构镜像构建过程的场景,也可以自动化生成Dockerfile。

使用dfimage工具

1
2
3
4
5
6
7
# 一键生成近似的Dockerfile
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
alpine/dfimage -sV=1.36 nginx:latest

# 或使用whaler
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
pegleg/whaler nginx:latest > Dockerfile.approximate

一键式集成脚本

结合上述工具,可以创建一个完整的自动化迁移脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/bash
# migrate-docker-to-compose.sh
# 一键从现有容器迁移到编排配置

set -e

echo "🔍 开始Docker容器逆向迁移"

# 1. 使用docker-autocompose生成基础配置
echo "📦 生成docker-compose.yml..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/red5d/docker-autocompose:latest $(docker ps -a --format "{{.Names}}") \
> docker-compose.yml

# 2. 生成环境变量模板
echo "🔐 创建环境变量模板..."
cat > .env.example << 'EOF'
# 自动生成的环境变量配置
# 请根据实际情况修改以下值
EOF

# 3. 生成各服务的Dockerfile(如需要)
echo "🐳 生成服务Dockerfile..."
for service in $(grep -E '^ [a-zA-Z]' docker-compose.yml | sed 's/://'); do
image=$(grep -A5 "^ $service:" docker-compose.yml | grep "image:" | cut -d: -f2- | tr -d ' ')
if [ -n "$image" ]; then
echo "# 为 $service 生成Dockerfile参考"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
alpine/dfimage -sV=1.36 $image > "Dockerfile.$service" 2>/dev/null || true
fi
done

echo "✅ 迁移完成!"
echo "📁 生成的文件:"
echo " - docker-compose.yml (主编排文件)"
echo " - .env.example (环境变量模板)"
echo " - Dockerfile.* (各服务构建参考)"
echo ""
echo "🚀 使用方式:"
echo " 1. 编辑 .env 文件,设置实际值"
echo " 2. 运行: docker-compose up -d"
echo " 3. 验证: docker-compose ps"

验证与测试

生成配置后,务必进行验证:

1
2
3
4
5
6
7
8
9
10
# 语法验证
docker-compose config

# 测试启动(不实际运行)
docker-compose up --dry-run

# 实际测试运行
docker-compose up -d
docker-compose ps
docker-compose logs

总结

通过自动化工具,原本需要数小时的手动配置工作可以在几分钟内完成。关键工具包括:

  1. docker-autocompose:命令行工具,适合批量处理和脚本集成
  2. compose-generator:交互式工具,适合新手和复杂场景
  3. dfimage/whaler:Dockerfile逆向生成,用于镜像重构

自动化逆向工程不仅提高了迁移效率,更重要的是建立了标准的、可版本控制的部署配置。无论是应对服务器迁移、环境标准化,还是团队协作,这套自动化方案都能显著降低运维成本,提升部署的可靠性

附加

我借助AI生成了一套交互式逆推脚本,它可以将本机所有已创建的容器进行逆推,或者指定具体某几个容器,脚本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
 #!/bin/bash
# docker-compose-archaeologist-final-fixed.sh
# 最终修复版:修复序号选择和版本检测
# 版本:v2.4-final-fixed

set -e

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'
BOLD='\033[1m'
UNDERLINE='\033[4m'

# 输出函数
error() { echo -e "${RED}✗ $1${NC}" >&2; }
success() { echo -e "${GREEN}✓ $1${NC}"; }
info() { echo -e "${BLUE}ℹ $1${NC}"; }
warning() { echo -e "${YELLOW}⚠ $1${NC}"; }
step() { echo -e "\n${CYAN}▶ $1${NC}"; }
header() { echo -e "\n${MAGENTA}▬▬▬▬▬▬▬▬▬▬ $1 ▬▬▬▬▬▬▬▬▬▬${NC}"; }

# 配置
AUTOCOMPOSE_IMAGE="ghcr.io/red5d/docker-autocompose:latest"
OUTPUT_FILE="docker-compose.yml"
TEMP_FILE="docker-compose.tmp.yml"
BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# 打印横幅
print_banner() {
clear
echo -e "${BOLD}${CYAN}"
cat << "EOF"
╔══════════════════════════════════════════════════════════════╗
║ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ║
║ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ ║
║ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌ ▀▀▀▀█░█▀▀▀▀ ║
║ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ║
║ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌ ▐░▌ ║
║ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ ▐░▌ ║
║ ▀▀▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀█░█▀▀ ▐░█▀▀▀▀▀▀▀█░▌ ▐░▌ ║
║ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ║
║ ▄▄▄▄▄▄▄▄▄█░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▄▄▄▄█░█▄▄▄▄ ║
║ ▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌ ║
║ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ║
║ ║
║ Docker Compose 考古学家 v2.4 ║
║ 自动逆向生成版本化编排配置 ║
╚══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"

echo -e "${YELLOW}📦 自动发现并迁移 Docker 容器到编排配置${NC}"
echo -e "${YELLOW}🔍 支持版本号自动检测和替换${NC}"
echo ""
}

# 检查是否安装jq
check_jq() {
if command -v jq &> /dev/null; then
HAS_JQ=true
JQ_VERSION=$(jq --version | grep -oE '[0-9]+\.[0-9]+')
success "jq $JQ_VERSION (JSON处理已启用)"
else
HAS_JQ=false
warning "未检测到 jq,使用基础功能 (建议安装: sudo apt install jq)"
fi
echo ""
}

# 获取所有容器
get_all_containers() {
step "扫描 Docker 容器"

if [ "$HAS_JQ" = true ]; then
CONTAINER_DATA=$(docker ps -a --format '{"Names":"{{.Names}}","Image":"{{.Image}}","Status":"{{.Status}}","State":"{{.State}}"}' | jq -s '.')
CONTAINER_COUNT=$(echo "$CONTAINER_DATA" | jq length)
echo "使用jq模式,找到 $CONTAINER_COUNT 个容器"
else
CONTAINER_LIST=$(docker ps -a --format "{{.Names}}\t{{.Image}}\t{{.Status}}")
CONTAINER_COUNT=$(echo "$CONTAINER_LIST" | wc -l | tr -d ' ')
echo "使用基础模式,找到 $CONTAINER_COUNT 个容器"
fi

if [ "$CONTAINER_COUNT" -eq 0 ] || [ -z "$CONTAINER_COUNT" ]; then
error "未发现任何Docker容器"
exit 1
fi

success "发现 $CONTAINER_COUNT 个容器"
}

# 显示容器列表
display_containers() {
header "容器列表"

echo "┌─────┬──────────────────────┬──────────────────────────┬─────────────────┐"
echo "│ 序号 │ 容器名称 │ 镜像 │ 状态 │"
echo "├─────┼──────────────────────┼──────────────────────────┼─────────────────┤"

if [ "$HAS_JQ" = true ]; then
for i in $(seq 0 $((CONTAINER_COUNT-1))); do
name=$(echo "$CONTAINER_DATA" | jq -r ".[$i].Names")
image=$(echo "$CONTAINER_DATA" | jq -r ".[$i].Image")
status=$(echo "$CONTAINER_DATA" | jq -r ".[$i].Status")

name_display=$(printf "%-20s" "$name" | cut -c1-20)
image_display=$(printf "%-24s" "$image" | cut -c1-24)
status_display=$(printf "%-15s" "$status" | cut -c1-15)

printf "│ %3d │ %-20s │ %-24s │ %-15s │\n" \
$((i+1)) "$name_display" "$image_display" "$status_display"
done
else
i=1
while IFS=$'\t' read -r name image status; do
[ -z "$name" ] && continue

name_display=$(printf "%-20s" "$name" | cut -c1-20)
image_display=$(printf "%-24s" "$image" | cut -c1-24)
status_display=$(printf "%-15s" "$status" | cut -c1-15)

printf "│ %3d │ %-20s │ %-24s │ %-15s │\n" \
"$i" "$name_display" "$image_display" "$status_display"
((i++))
done <<< "$CONTAINER_LIST"
fi

echo "└─────┴──────────────────────┴──────────────────────────┴─────────────────┘"
echo ""
}

# 选择模式
select_mode() {
header "选择模式"

echo "请选择操作方式:"
echo ""
echo " 1) 📋 选择所有容器 ($CONTAINER_COUNT 个)"
echo " 2) 🔢 选择特定容器 (支持单个、多个、范围选择)"
echo " 3) 🎯 按状态筛选 (运行中/已停止)"
echo " 4) 🔍 搜索容器"
echo " 5) ❌ 退出"
echo ""

read -p "请输入选择 [1-5]: " mode

case $mode in
1)
select_all_containers
;;
2)
select_specific_containers
;;
3)
select_by_status
;;
4)
search_containers
;;
5)
info "退出程序"
exit 0
;;
*)
error "无效选择,使用默认模式"
select_all_containers
;;
esac
}

# 选择所有容器
select_all_containers() {
info "选择所有容器"

if [ "$HAS_JQ" = true ]; then
SELECTED_CONTAINERS=($(echo "$CONTAINER_DATA" | jq -r '.[].Names'))
else
SELECTED_CONTAINERS=()
while IFS=$'\t' read -r name _; do
[ -n "$name" ] && SELECTED_CONTAINERS+=("$name")
done <<< "$CONTAINER_LIST"
fi

echo "已选择 ${#SELECTED_CONTAINERS[@]} 个容器"
}

# 解析范围选择
parse_range_selection() {
local selection="$1"
local result=()

IFS=',' read -ra parts <<< "$selection"
for part in "${parts[@]}"; do
part=$(echo "$part" | tr -d ' ')

if [[ "$part" =~ ^([0-9]+)-([0-9]+)$ ]]; then
start=${BASH_REMATCH[1]}
end=${BASH_REMATCH[2]}

if [ "$start" -le "$end" ] && [ "$start" -ge 1 ] && [ "$end" -le "$CONTAINER_COUNT" ]; then
for ((i=start; i<=end; i++)); do
result+=("$i")
done
else
warning "范围 $part 无效,跳过"
fi
elif [[ "$part" =~ ^[0-9]+$ ]]; then
result+=("$part")
else
warning "无效输入: $part,跳过"
fi
done

echo "${result[@]}" | tr ' ' '\n' | sort -nu | tr '\n' ' '
}

# 修复:选择特定容器 - 确保不会直接退出
select_specific_containers() {
while true; do
info "选择特定容器"

display_containers

echo ""
echo "📝 选择方式:"
echo " - 单个容器: 1"
echo " - 多个容器: 1 3 5"
echo " - 范围选择: 1-5"
echo " - 混合选择: 1,3-5,7"
echo " - 全部选择: all"
echo " - 返回菜单: back"
echo ""

read -p "请输入选择: " selection

if [ "$selection" = "back" ]; then
info "返回主菜单"
select_mode
return
fi

if [ "$selection" = "all" ]; then
select_all_containers
return
fi

if [ -z "$selection" ]; then
error "输入为空,请重新输入"
continue
fi

parsed_selection=$(parse_range_selection "$selection")

if [ -z "$parsed_selection" ]; then
error "无效的选择,请重新输入"
continue
fi

SELECTED_CONTAINERS=()
selected_count=0

for num in $parsed_selection; do
idx=$((num-1))

if [ "$HAS_JQ" = true ]; then
if [ $idx -ge 0 ] && [ $idx -lt $CONTAINER_COUNT ]; then
container=$(echo "$CONTAINER_DATA" | jq -r ".[$idx].Names")
SELECTED_CONTAINERS+=("$container")
((selected_count++))
success "✓ 已选择: $container"
else
warning "⚠ 序号 $num 无效,跳过"
fi
else
container=$(echo "$CONTAINER_LIST" | sed -n "${num}p" | cut -f1)
if [ -n "$container" ]; then
SELECTED_CONTAINERS+=("$container")
((selected_count++))
success "✓ 已选择: $container"
else
warning "⚠ 序号 $num 无效,跳过"
fi
fi
done

if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
error "未选择任何有效容器,请重新输入"
continue
fi

success "已选择 $selected_count 个容器"

# 显示选择结果
echo ""
echo "📋 已选择的容器:"
for container in "${SELECTED_CONTAINERS[@]}"; do
echo " - $container"
done
echo ""

# 修复:添加确认步骤,但确保不会因为确认而退出
read -p "确认选择? [Y/重新选择(r)/返回菜单(b)]: " confirm

case $confirm in
[rR])
info "重新选择"
continue
;;
[bB])
info "返回主菜单"
select_mode
return
;;
*)
# 用户确认,继续执行
return
;;
esac
done
}

# 按状态筛选
select_by_status() {
while true; do
info "按状态筛选"

echo "请选择状态:"
echo " 1) 🟢 运行中 (running)"
echo " 2) 🔴 已停止 (exited)"
echo " 3) 🟡 所有状态"
echo " 4) ↩ 返回菜单"
echo ""

read -p "请输入 [1-4]: " status_choice

case $status_choice in
1)
status_filter="Up"
status_display="运行中"
;;
2)
status_filter="Exited"
status_display="已停止"
;;
3)
status_filter="all"
status_display="所有状态"
;;
4)
info "返回主菜单"
select_mode
return
;;
*)
error "无效选择"
continue
;;
esac

SELECTED_CONTAINERS=()

if [ "$status_filter" = "all" ]; then
select_all_containers
return
fi

if [ "$HAS_JQ" = true ]; then
if [ "$status_filter" = "Up" ]; then
SELECTED_CONTAINERS=($(echo "$CONTAINER_DATA" | jq -r ".[] | select(.Status | startswith(\"Up\")) | .Names"))
elif [ "$status_filter" = "Exited" ]; then
SELECTED_CONTAINERS=($(echo "$CONTAINER_DATA" | jq -r ".[] | select(.Status | startswith(\"Exited\")) | .Names"))
fi
else
while IFS=$'\t' read -r name image status; do
[ -z "$name" ] && continue

if [ "$status_filter" = "Up" ] && [[ "$status" == Up* ]]; then
SELECTED_CONTAINERS+=("$name")
elif [ "$status_filter" = "Exited" ] && [[ "$status" == Exited* ]]; then
SELECTED_CONTAINERS+=("$name")
fi
done <<< "$CONTAINER_LIST"
fi

if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
warning "未找到状态为 '$status_display' 的容器"
echo ""
read -p "是否重新选择? [Y/n] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
continue
else
select_mode
return
fi
else
success "找到 ${#SELECTED_CONTAINERS[@]} 个 '$status_display' 状态的容器"

echo ""
echo "📋 找到的容器:"
for container in "${SELECTED_CONTAINERS[@]}"; do
echo " - $container"
done
echo ""

read -p "确认选择? [Y/n/重新选择(r)]: " confirm
case $confirm in
[nN]|[rR])
continue
;;
*)
return
;;
esac
fi
done
}

# 搜索容器
search_containers() {
while true; do
info "搜索容器"

echo "搜索选项:"
echo " 1) 搜索容器名称"
echo " 2) 搜索镜像名称"
echo " 3) 搜索全部字段"
echo " 4) ↩ 返回菜单"
echo ""

read -p "请选择搜索模式 [1-4]: " search_mode

case $search_mode in
1) search_type="name" ;;
2) search_type="image" ;;
3) search_type="all" ;;
4)
info "返回主菜单"
select_mode
return
;;
*)
error "无效选择"
continue
;;
esac

read -p "请输入搜索关键词: " keyword

if [ -z "$keyword" ]; then
warning "搜索词为空,返回所有容器"
select_all_containers
return
fi

SELECTED_CONTAINERS=()

if [ "$HAS_JQ" = true ]; then
case $search_type in
"name")
SELECTED_CONTAINERS=($(echo "$CONTAINER_DATA" | jq -r ".[] | select(.Names | contains(\"$keyword\")) | .Names"))
;;
"image")
SELECTED_CONTAINERS=($(echo "$CONTAINER_DATA" | jq -r ".[] | select(.Image | contains(\"$keyword\")) | .Names"))
;;
"all")
SELECTED_CONTAINERS=($(echo "$CONTAINER_DATA" | jq -r ".[] | select(.Names | contains(\"$keyword\")) or select(.Image | contains(\"$keyword\")) | .Names"))
;;
esac
else
while IFS=$'\t' read -r name image status; do
[ -z "$name" ] && continue

local match=false
case $search_type in
"name")
[[ "$name" == *"$keyword"* ]] && match=true
;;
"image")
[[ "$image" == *"$keyword"* ]] && match=true
;;
"all")
[[ "$name" == *"$keyword"* ]] || [[ "$image" == *"$keyword"* ]] && match=true
;;
esac

[ "$match" = true ] && SELECTED_CONTAINERS+=("$name")
done <<< "$CONTAINER_LIST"
fi

if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
warning "未找到包含 '$keyword' 的容器"
echo ""
read -p "是否重新搜索? [Y/n] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
continue
else
select_mode
return
fi
else
success "找到 ${#SELECTED_CONTAINERS[@]} 个匹配的容器"

echo ""
echo "🔍 搜索结果:"
for container in "${SELECTED_CONTAINERS[@]}"; do
echo " - $container"
done
echo ""

read -p "确认选择? [Y/n/重新搜索(r)]: " confirm
case $confirm in
[nN]|[rR])
continue
;;
*)
return
;;
esac
fi
done
}

# 备份现有文件
backup_existing() {
if [ -f "$OUTPUT_FILE" ]; then
backup_file="${OUTPUT_FILE}.backup.$BACKUP_TIMESTAMP"
cp "$OUTPUT_FILE" "$backup_file"
info "已备份现有文件: $backup_file"
fi
}

# 优化:使用简单的docker inspect获取版本号
get_image_version_simple() {
local image_name="$1"
local image_tag="$2"
local full_image="${image_name}:${image_tag}"

if ! docker image inspect "$full_image" &> /dev/null; then
echo ""
return
fi

# 使用简单的 docker inspect | grep -i version 方法
local version_info=$(docker image inspect "$full_image" 2>/dev/null | \
grep -i "version" | \
grep -oE '[0-9]+\.[0-9]+\.[0-9]+|[0-9]+\.[0-9]+' | \
head -1)

if [ -n "$version_info" ]; then
echo "$version_info"
return
fi

# 尝试从常见的环境变量中获取
local env_version=$(docker image inspect "$full_image" 2>/dev/null | \
grep -oE '"VERSION="[^"]*|"NGINX_VERSION="[^"]*|"POSTGRES_VERSION="[^"]*|"MYSQL_VERSION="[^"]*|"REDIS_VERSION="[^"]*' | \
cut -d'"' -f4 | \
head -1)

if [ -n "$env_version" ]; then
echo "$env_version"
return
fi

# 从创建时间推断
local created_date=$(docker image inspect "$full_image" 2>/dev/null | \
grep -o '"Created"[^,]*' | \
cut -d '"' -f4 | \
head -1 | \
cut -d'T' -f1)

if [ -n "$created_date" ]; then
echo "$created_date" | grep -oE '^[0-9]{4}-[0-9]{2}' | tr '-' '.'
return
fi

echo ""
}

# 替换版本号
replace_versions() {
step "版本号考古与替换"

mapfile -t IMAGE_LINES < <(grep -n -E '^\s+image:' "$TEMP_FILE" 2>/dev/null || true)

if [ ${#IMAGE_LINES[@]} -eq 0 ]; then
warning "未找到镜像定义"
return
fi

info "发现 ${#IMAGE_LINES[@]} 个镜像"

cp "$TEMP_FILE" "$OUTPUT_FILE"

local replaced_count=0

for line_info in "${IMAGE_LINES[@]}"; do
line_num=$(echo "$line_info" | cut -d: -f1)
line_content=$(echo "$line_info" | cut -d: -f2-)

image_spec=$(echo "$line_content" | sed 's/^[[:space:]]*//;s/image://;s/[[:space:]]*$//;s/#.*$//')

[ -z "$image_spec" ] && continue

if [[ "$image_spec" == *:* ]]; then
image_name="${image_spec%:*}"
current_tag="${image_spec#*:}"
else
image_name="$image_spec"
current_tag="latest"
fi

image_name=$(echo "$image_name" | sed "s/^['\"]//;s/['\"]$//")
current_tag=$(echo "$current_tag" | sed "s/^['\"]//;s/['\"]$//")

if [[ "$current_tag" == "latest" ]]; then
# 使用优化后的版本检测方法
version=$(get_image_version_simple "$image_name" "$current_tag")

if [ -n "$version" ] && [ "$version" != "latest" ]; then
# 显示调试信息
echo -e " ${BLUE}ℹ${NC} 检测镜像: ${image_name}:${current_tag}"
echo -e " ${GREEN}✓${NC} 找到版本: $version"

# 替换latest为具体版本
sed -i "s|^\(\s*image:\s*\)${image_name}:latest\b|\1${image_name}:${version}|g" "$OUTPUT_FILE"
sed -i "s|^\(\s*image:\s*\)['\"]\?${image_name}:latest['\"]\?\b|\1\"${image_name}:${version}\"|g" "$OUTPUT_FILE"

echo -e " ${GREEN}✓${NC} 替换: ${image_name}:latest → ${image_name}:${version}"
((replaced_count++))
else
echo -e " ${YELLOW}⚠${NC} 无法确定版本: ${image_name}:latest (保持原样)"
fi
else
echo -e " ${BLUE}ℹ${NC} 已有具体版本: ${image_name}:${current_tag}"
fi
done

if [ $replaced_count -gt 0 ]; then
success "版本替换完成 ($replaced_count 个镜像已更新)"
else
info "没有需要替换的 latest 标签"
fi
}

# 生成Compose配置
generate_compose() {
local containers=("$@")

header "生成配置"

step "验证容器"
local valid_containers=()

for container in "${containers[@]}"; do
if docker inspect "$container" &> /dev/null; then
valid_containers+=("$container")
success " ✓ $container"
else
error " ✗ $container (不存在)"
fi
done

if [ ${#valid_containers[@]} -eq 0 ]; then
error "没有有效的容器可分析"
exit 1
fi

backup_existing

step "调用 AutoCompose"
echo "正在分析: ${valid_containers[*]}"

if ! docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
"$AUTOCOMPOSE_IMAGE" "${valid_containers[@]}" > "$TEMP_FILE" 2>&1; then
error "AutoCompose执行失败"
echo "错误信息:"
cat "$TEMP_FILE"
exit 1
fi

if [ ! -s "$TEMP_FILE" ]; then
error "生成的配置为空"
exit 1
fi

success "原始配置生成完成 ($(wc -l < "$TEMP_FILE") 行)"

replace_versions

header "完成"

echo -e "${GREEN}📁 输出文件: $OUTPUT_FILE${NC}"
echo -e "${BLUE}📊 文件大小: $(du -h "$OUTPUT_FILE" | cut -f1)${NC}"
echo -e "${BLUE}📈 总行数: $(wc -l < "$OUTPUT_FILE")${NC}"
echo -e "${BLUE}📦 容器数量: ${#valid_containers[@]}${NC}"
echo ""

echo -e "${YELLOW}🔍 生成的镜像配置:${NC}"
grep -E '^\s+image:' "$OUTPUT_FILE" 2>/dev/null | head -20 | sed 's/^/ /'

echo ""
echo -e "${CYAN}💡 使用建议:${NC}"
echo " 1. 🔍 检查配置: vim $OUTPUT_FILE"
echo " 2. ✅ 验证语法: docker-compose -f $OUTPUT_FILE config"
echo " 3. 🚀 测试启动: docker-compose -f $OUTPUT_FILE up -d"
echo " 4. 📊 查看状态: docker-compose -f $OUTPUT_FILE ps"
echo " 5. 🛑 停止清理: docker-compose -f $OUTPUT_FILE down"
echo ""

rm -f "$TEMP_FILE"

echo -e "${MAGENTA}┌──────────────────────────────────────────────────────┐${NC}"
echo -e "${MAGENTA}│ 📁 文件位置: $(pwd)/$OUTPUT_FILE${NC}"
echo -e "${MAGENTA}└──────────────────────────────────────────────────────┘${NC}"
}

# 修复:主函数 - 确保选择后不会直接退出
main() {
print_banner

check_jq

get_all_containers

display_containers

select_mode

# 修复:检查SELECTED_CONTAINERS是否为空
if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
error "未选择任何容器,退出程序"
exit 1
fi

echo ""
echo -e "${CYAN}已选择以下 ${#SELECTED_CONTAINERS[@]} 个容器:${NC}"
for container in "${SELECTED_CONTAINERS[@]}"; do
echo " - $container"
done
echo ""

read -p "确认开始生成配置? [Y/n] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Nn]$ ]]; then
info "操作取消"
exit 0
fi

generate_compose "${SELECTED_CONTAINERS[@]}"
}

# 异常处理
trap 'echo ""; error "脚本被中断"; exit 1' INT TERM

# 运行主函数
main "$@"

将上面代码拷贝到xxx.sh 文件中,然后./xxx.sh 运行即可,运行前记得给予它执行权限:

1
chmod +x xxx.sh

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Docker Compose 常用命令汇总

发表于 2026-04-22

核心生命周期管理

1
2
# 创建并启动所有服务
docker compose up -d

行为说明:容器不存在则创建,配置变更则重建,否则直接启动

1
2
# 重新构建镜像并启动
docker compose up -d --build

行为说明:强制重新构建镜像并用新镜像创建容器

1
2
# 停止并删除所有容器、网络
docker compose down

行为说明:停止删除容器和网络,保留卷和镜像

1
2
# 停止容器不删除
docker compose stop

行为说明:仅停止容器,保留容器和数据

1
2
# 启动已停止的服务
docker compose start
1
2
# 重启所有服务
docker compose restart

容器操作

1
2
# 进入容器 Shell
docker compose exec 服务名 sh
1
2
# 查看服务日志
docker compose logs -f 服务名

行为说明:-f 参数持续跟踪日志输出

1
2
# 查看容器状态
docker compose ps

行为说明:默认只显示运行中的容器,-a 查看所有

镜像构建与管理

1
2
# 仅构建镜像
docker compose build
1
2
# 强制重建镜像
docker compose build --no-cache

行为说明:忽略所有构建缓存,从头开始

1
2
# 拉取服务镜像
docker compose pull

监控与日志

1
2
# 追踪所有日志
docker compose logs -f
1
2
# 查看容器进程
docker compose top

状态与信息查询

1
2
# 验证配置语法
docker compose config

行为说明:解析显示最终配置,验证语法

1
2
# 查看端口映射
docker compose port 服务名 端口

清理与维护

1
2
# 彻底清理(包括卷)
docker compose down -v

行为说明:删除容器、网络、卷,数据会丢失

1
2
# 清理构建缓存
docker builder prune

调试与开发

1
2
# 前台运行调试
docker compose up

行为说明:前台运行,容器退出则停止

1
2
# 执行一次性命令
docker compose run --rm 服务名 命令

行为说明:创建临时容器执行命令,完成后自动删除

项目与上下文管理

1
2
# 使用自定义 Compose 文件
docker compose -f docker-compose-dev.yml up
1
2
# 指定项目名称
docker compose -p 项目名 up

常用场景组合

开发调试

1
2
# 代码变更后重建
docker compose up -d --build
1
2
# 查看最近日志
docker compose logs --tail=20

生产更新

1
2
# 拉取最新镜像
docker compose pull
1
2
# 滚动更新
docker compose up -d

行为说明:用新镜像替换旧容器,实现无停机更新

完全重置

1
2
# 彻底清理
docker compose down -v
1
2
# 从头开始
docker compose up -d --build

一键脚本示例

开发环境重启脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# dev-restart.sh
echo "停止服务..."
docker compose stop

echo "清理旧容器..."
docker compose rm -f

echo "清理未使用的镜像..."
docker image prune -f

echo "重新构建并启动..."
docker compose up -d --build

echo "查看日志..."
docker compose logs -f

生产部署脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
# deploy.sh
set -e # 出错时退出

echo "1. 拉取最新镜像..."
docker compose pull

echo "2. 备份数据库..."
docker compose exec -T db pg_dumpall -U postgres > backup_$(date +%Y%m%d_%H%M%S).sql

echo "3. 执行数据库迁移..."
docker compose run --rm web python manage.py migrate

echo "4. 重启服务..."
docker compose up -d

echo "5. 健康检查..."
sleep 30
if docker compose ps | grep -q "unhealthy"; then
echo "❌ 服务健康检查失败"
docker compose logs --tail=50
exit 1
fi

echo "✅ 部署成功"

监控脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# monitor.sh
echo "=== Docker Compose 服务状态 ==="
docker compose ps

echo -e "\n=== 资源使用情况 ==="
docker compose stats --no-stream

echo -e "\n=== 最近错误日志 ==="
docker compose logs --tail=100 2>/dev/null | grep -E "(ERROR|ERR|Error)" | tail -20

echo -e "\n=== 服务健康状态 ==="
for service in $(docker compose ps --services); do
status=$(docker compose ps $service | tail -1 | awk '{print $4}')
echo "$service: $status"
done

最佳实践

黄金法则

  • up -d --build - 开发时用,确保代码变更生效
  • pull + up -d - 生产更新用,实现滚动更新
  • logs -f - 调试时用,实时查看日志
  • down -v - 清理时用,彻底重置环境

重要提示

  • 容器内修改会在重建后丢失
  • 重要数据要使用卷持久化
  • 配置文件变更会导致容器重建
  • 镜像标签不变时不会自动拉取新版本

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Docker 环境下 MySQL 数据库备份与查看操作指南

发表于 2026-04-21

前言

在使用 Docker 部署 MySQL 时,很多开发者习惯进入容器内部进行操作,然而这常常带来以下痛点:

  • 备份文件默认保存在容器内部,容器一旦停止或删除,备份随之丢失
  • 操作步骤繁琐,需要在容器内外反复切换终端
  • 不利于自动化脚本编写,手动操作易出错
  • 使用磁盘目录映射,要更加方便一些,但依然不够优雅

本文将系统介绍如何在 Docker 环境中安全、高效地备份和查看 MySQL 数据库,并提供”不进容器”的最佳实践方案,同时提供完整的自动化备份和恢复脚本。


一、备份操作

1.1 容器内部备份(不推荐但需了解)

如果你已经进入 MySQL 容器(docker exec -it mysql bash),备份流程如下:

1
2
3
4
5
6
7
8
# 备份单个数据库到容器内临时目录
mysqldump -u root -p密码 数据库名 > /tmp/backup.sql

# 备份所有数据库
mysqldump -u root -p密码 --all-databases > /tmp/all_backup.sql

# 备份并压缩
mysqldump -u root -p密码 数据库名 | gzip > /tmp/backup.sql.gz

关键问题:备份文件在容器内部,容器删除则文件丢失。

解决方法:将文件从容器复制到宿主机

1
2
# 在宿主机执行(另一个终端窗口)
docker cp 容器名或ID:/tmp/backup.sql /宿主机/目标路径/

完整工作流示例:

1
2
3
4
5
6
7
8
# 1. 容器内备份
root@容器ID:/# mysqldump -u root -p123456 mydb > /tmp/mydb_backup.sql

# 2. 宿主机复制(新终端窗口)
$ docker cp mysql-container:/tmp/mydb_backup.sql ~/backups/

# 3. 清理容器临时文件
root@容器ID:/# rm /tmp/mydb_backup.sql

1.2 推荐方案:不进容器直接备份

直接在宿主机执行,一步到位:

1
2
3
4
5
6
7
8
# 备份单个数据库到宿主机
docker exec mysql-container mysqldump -u root -p密码 数据库名 > ~/backup.sql

# 备份所有数据库
docker exec mysql-container mysqldump -u root -p密码 --all-databases > ~/all_backup.sql

# 备份并压缩
docker exec mysql-container mysqldump -u root -p密码 数据库名 | gzip > ~/backup.sql.gz

为什么这是最佳实践?

  • ✅ 简单:单条命令完成备份
  • ✅ 安全:备份直接保存在宿主机
  • ✅ 自动化:易于集成到脚本和定时任务
  • ✅ 资源友好:不占用容器内磁盘空间

1.3 自动化备份脚本

对于生产环境,建议使用自动化备份脚本。以下是一个功能完整的双重备份脚本,可保存为 backup-mysql.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#!/bin/bash
# MySQL 双重备份脚本
# 1. 完整备份所有数据库
# 2. 每个数据库单独备份
# 3. 保留最近3份备份

# ============ 配置区域 ============
CONTAINER_NAME="c_mysql" # MySQL容器名
MYSQL_USER="root" # MySQL用户名
MYSQL_PASSWORD="songjian" # MySQL密码
BACKUP_BASE_DIR="/root/songjian/mysql/backups" # 备份根目录
KEEP_BACKUPS=3 # 保留的备份份数
LOG_FILE="/root/songjian/mysql/backup-mysql.log" # 日志文件
# ==================================

# 创建必要的目录
mkdir -p $BACKUP_BASE_DIR
mkdir -p $(dirname $LOG_FILE)

# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}

# 错误处理函数
error_exit() {
log "❌ 错误: $1"
exit 1
}

# 获取当前时间戳
BACKUP_TIME=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="$BACKUP_BASE_DIR/$BACKUP_TIME"

log "=========================================="
log "开始 MySQL 双重备份"
log "备份时间: $BACKUP_TIME"
log "备份目录: $BACKUP_DIR"
log "容器名称: $CONTAINER_NAME"
log "备份路径: $BACKUP_BASE_DIR"

# 检查容器是否运行
CONTAINER_STATUS=$(docker ps --filter "name=$CONTAINER_NAME" --format "{{.Names}}")

if [ -z "$CONTAINER_STATUS" ]; then
error_exit "容器 ${CONTAINER_NAME} 未运行!"
else
log "✅ 容器状态: 运行中 ($CONTAINER_NAME)"
fi

# 创建本次备份目录
mkdir -p $BACKUP_DIR/all_databases
mkdir -p $BACKUP_DIR/separate_databases
mkdir -p $BACKUP_DIR/logs

# 1. 备份所有数据库(完整备份)
log "1. 开始完整备份所有数据库..."
ALL_BACKUP_FILE="$BACKUP_DIR/all_databases/full_backup.sql.gz"

# 测试 MySQL 连接
log "测试 MySQL 连接..."
if ! docker exec $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT 1" >/dev/null 2>&1; then
error_exit "无法连接到 MySQL 数据库,请检查用户名和密码"
else
log "✅ MySQL 连接测试成功"
fi

# 执行完整备份
docker exec $CONTAINER_NAME mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD \
--all-databases \
--single-transaction \
--routines \
--events \
--triggers \
--set-gtid-purged=OFF 2>> "$BACKUP_DIR/logs/full_backup.log" | gzip > $ALL_BACKUP_FILE

if [ $? -eq 0 ] && [ -s $ALL_BACKUP_FILE ]; then
SIZE=$(du -h $ALL_BACKUP_FILE | cut -f1)
log " ✅ 完整备份成功: $(basename $ALL_BACKUP_FILE) ($SIZE)"
else
# 尝试不使用 --set-gtid-purged 参数
log "⚠️ 第一次备份失败,尝试不使用 GTID 参数..."
docker exec $CONTAINER_NAME mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD \
--all-databases \
--single-transaction \
--routines \
--events \
--triggers 2>> "$BACKUP_DIR/logs/full_backup_alt.log" | gzip > $ALL_BACKUP_FILE

if [ $? -eq 0 ] && [ -s $ALL_BACKUP_FILE ]; then
SIZE=$(du -h $ALL_BACKUP_FILE | cut -f1)
log " ✅ 完整备份成功(备选方案): $(basename $ALL_BACKUP_FILE) ($SIZE)"
else
error_exit "完整备份失败!"
fi
fi

# 2. 获取所有数据库列表
log "2. 获取数据库列表..."
DATABASES=$(docker exec $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD \
--skip-column-names \
-e "SHOW DATABASES;" 2>> "$BACKUP_DIR/logs/db_list.log")

if [ $? -ne 0 ]; then
error_exit "获取数据库列表失败!"
fi

# 过滤掉系统数据库
FILTERED_DATABASES=""
for DB in $DATABASES; do
case $DB in
information_schema|performance_schema|sys|mysql)
log " 跳过系统数据库: $DB"
;;
*)
FILTERED_DATABASES="$FILTERED_DATABASES $DB"
;;
esac
done

# 重新赋值
DATABASES=$FILTERED_DATABASES
DB_COUNT=$(echo "$DATABASES" | wc -w)

log " 找到 $DB_COUNT 个用户数据库需要单独备份"

# 如果没有数据库,只备份完整备份
if [ $DB_COUNT -eq 0 ]; then
log "⚠️ 没有找到用户数据库,只保留完整备份"

# 创建备份摘要
cat > "$BACKUP_DIR/backup_summary.txt" << EOF
备份摘要
========
备份时间: $(date)
备份编号: $BACKUP_TIME
备份目录: $BACKUP_DIR
容器名称: $CONTAINER_NAME

备份内容:
1. 完整备份: $ALL_BACKUP_FILE
大小: $(du -h $ALL_BACKUP_FILE | cut -f1)

2. 单独数据库备份: 0 个 (没有找到用户数据库)

备份统计:
数据库总数: 0
完整备份: 成功
单独备份: 跳过
EOF

# 清理旧备份
cleanup_old_backups
exit 0
fi

log " 需要备份的数据库: $DATABASES"

# 3. 逐个备份每个数据库
SUCCESS_COUNT=0
FAIL_COUNT=0
FAILED_DBS=""
log "3. 开始逐个数据库备份..."

for DB in $DATABASES; do
log " 备份数据库: $DB"
DB_BACKUP_FILE="$BACKUP_DIR/separate_databases/${DB}.sql.gz"

docker exec $CONTAINER_NAME mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD \
--single-transaction \
--routines \
--events \
--triggers \
$DB 2>> "$BACKUP_DIR/logs/${DB}.log" | gzip > $DB_BACKUP_FILE

if [ $? -eq 0 ] && [ -s $DB_BACKUP_FILE ]; then
SIZE=$(du -h $DB_BACKUP_FILE | cut -f1)
log " ✅ 成功 ($SIZE)"
((SUCCESS_COUNT++))
else
log " ❌ 失败,尝试简单备份..."
# 尝试简化备份
docker exec $CONTAINER_NAME mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD \
$DB 2>> "$BACKUP_DIR/logs/${DB}_simple.log" | gzip > $DB_BACKUP_FILE

if [ $? -eq 0 ] && [ -s $DB_BACKUP_FILE ]; then
SIZE=$(du -h $DB_BACKUP_FILE | cut -f1)
log " ✅ 成功(简单模式) ($SIZE)"
((SUCCESS_COUNT++))
else
log " ❌❌ 备份失败"
rm -f $DB_BACKUP_FILE
FAILED_DBS="$FAILED_DBS $DB"
((FAIL_COUNT++))
fi
fi
done

log " 数据库单独备份完成: 成功 $SUCCESS_COUNT 个, 失败 $FAIL_COUNT 个"
if [ $FAIL_COUNT -gt 0 ]; then
log " 失败的数据库: $FAILED_DBS"
fi

# 4. 创建备份摘要
log "4. 生成备份摘要..."
SUMMARY_FILE="$BACKUP_DIR/backup_summary.txt"
cat > $SUMMARY_FILE << EOF
备份摘要
========
备份时间: $(date)
备份编号: $BACKUP_TIME
备份目录: $BACKUP_DIR
容器名称: $CONTAINER_NAME
MySQL 用户: $MYSQL_USER

备份内容:
1. 完整备份: $ALL_BACKUP_FILE
大小: $(du -h $ALL_BACKUP_FILE | cut -f1)

2. 单独数据库备份 ($SUCCESS_COUNT 个):
EOF

# 添加每个数据库的备份信息
for DB in $DATABASES; do
FILE="$BACKUP_DIR/separate_databases/${DB}.sql.gz"
if [ -f "$FILE" ]; then
SIZE=$(du -h "$FILE" 2>/dev/null | cut -f1 || echo "未知")
echo " - $DB: $SIZE" >> $SUMMARY_FILE
fi
done

cat >> $SUMMARY_FILE << EOF

备份统计:
数据库总数: $DB_COUNT
成功备份: $SUCCESS_COUNT
备份失败: $FAIL_COUNT
EOF

if [ -n "$FAILED_DBS" ]; then
echo " 失败列表: $FAILED_DBS" >> $SUMMARY_FILE
fi

cat >> $SUMMARY_FILE << EOF
日志文件: $BACKUP_DIR/logs/

生成时间: $(date)
EOF

# 5. 清理旧备份函数
cleanup_old_backups() {
log "5. 清理旧备份(保留最近 $KEEP_BACKUPS 份)..."
BACKUP_LIST=($(ls -dt $BACKUP_BASE_DIR/20* 2>/dev/null))
BACKUP_COUNT=${#BACKUP_LIST[@]}

if [ $BACKUP_COUNT -gt $KEEP_BACKUPS ]; then
TO_DELETE_COUNT=$((BACKUP_COUNT - KEEP_BACKUPS))
log " 发现 $BACKUP_COUNT 份备份,需要删除 $TO_DELETE_COUNT 份旧备份"

for ((i=KEEP_BACKUPS; i<BACKUP_COUNT; i++)); do
OLD_BACKUP=${BACKUP_LIST[$i]}
OLD_BACKUP_NAME=$(basename "$OLD_BACKUP")
log " 删除旧备份: $OLD_BACKUP_NAME"
rm -rf "$OLD_BACKUP"

# 记录删除操作
echo "删除备份: $OLD_BACKUP_NAME ($(date))" >> "$BACKUP_DIR/logs/cleanup.log"
done
else
log " 当前有 $BACKUP_COUNT 份备份,无需清理(最多保留 $KEEP_BACKUPS 份)"
fi
}

# 执行清理
cleanup_old_backups

# 6. 计算总大小
TOTAL_SIZE=$(du -sh $BACKUP_DIR 2>/dev/null | cut -f1 || echo "0B")
REMAINING_SIZE=$(du -sh $BACKUP_BASE_DIR 2>/dev/null | cut -f1 || echo "0B")
BACKUP_LIST_CURRENT=($(ls -dt $BACKUP_BASE_DIR/20* 2>/dev/null))
CURRENT_BACKUP_COUNT=${#BACKUP_LIST_CURRENT[@]}

log "6. 备份完成统计"
log " 本次备份大小: $TOTAL_SIZE"
log " 总备份占用: $REMAINING_SIZE"
log " 当前保留备份: $CURRENT_BACKUP_COUNT 份"
log " 备份目录: $BACKUP_BASE_DIR"
log " 备份日志: $LOG_FILE"

# 生成最终报告
log "=========================================="
log "✅ MySQL 双重备份完成!"
log " 备份编号: $BACKUP_TIME"
log " 完整备份: $(basename $ALL_BACKUP_FILE)"
log " 单独备份: $SUCCESS_COUNT 个数据库"
log " 保留备份: $CURRENT_BACKUP_COUNT/$KEEP_BACKUPS 份"

# 输出摘要
echo ""
echo "===== 备份摘要 ====="
cat $SUMMARY_FILE

设置定时任务:

1
2
3
4
5
# 编辑 crontab
crontab -e

# 添加以下行(每隔2天的凌晨2点执行)
0 2 */2 * * /bin/bash /root/songjian/mysql/backup-mysql.sh

二、查看操作(不进容器)

无需进入容器,直接在宿主机通过 docker exec 调用 MySQL 客户端。

2.1 查看数据库信息

1
2
3
4
5
# 查看所有数据库
docker exec mysql-container mysql -u root -p123456 -e "SHOW DATABASES;"

# 查看当前使用的数据库
docker exec mysql-container mysql -u root -p123456 -e "SELECT DATABASE();"

2.2 查看表结构

1
2
3
4
5
6
7
8
# 查看指定数据库的所有表
docker exec mysql-container mysql -u root -p123456 mydb -e "SHOW TABLES;"

# 查看表结构
docker exec mysql-container mysql -u root -p123456 mydb -e "DESC 表名;"

# 查看建表语句
docker exec mysql-container mysql -u root -p123456 mydb -e "SHOW CREATE TABLE 表名;"

2.3 数据查询

1
2
3
4
5
6
7
8
# 查询数据(限制5条)
docker exec mysql-container mysql -u root -p123456 mydb -e "SELECT * FROM users LIMIT 5;"

# 查询记录数
docker exec mysql-container mysql -u root -p123456 mydb -e "SELECT COUNT(*) FROM users;"

# 带条件的查询
docker exec mysql-container mysql -u root -p123456 mydb -e "SELECT id, name FROM users WHERE status=1;"

2.4 交互式模式

如需连续执行多条 SQL 命令,使用交互式模式:

1
2
# 进入 MySQL 交互命令行
docker exec -it mysql-container mysql -u root -p

输入密码后,进入 mysql> 提示符,可执行任何 SQL 命令:

1
2
3
4
USE mydb;
SHOW TABLES;
SELECT * FROM users;
EXIT; -- 退出

三、恢复操作

3.1 手动恢复

1
2
3
4
5
# 恢复完整备份
zcat backup.sql.gz | docker exec -i mysql-container mysql -u root -p密码

# 恢复单个数据库
zcat database.sql.gz | docker exec -i mysql-container mysql -u root -p密码 数据库名

3.2 交互式恢复脚本

对于复杂的恢复需求,可以使用以下交互式恢复脚本,保存为 interactive-restore.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#!/bin/bash
# 交互式 MySQL 恢复脚本

CONTAINER_NAME="c_mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="songjian"
BACKUP_BASE_DIR="/root/songjian/mysql/backups"

# 声明数组变量
declare -a BACKUP_PATHS

# 显示菜单
show_menu() {
clear
echo "========================================"
echo " MySQL 数据库恢复工具"
echo "========================================"
echo ""

# 列出备份
echo "可用的备份:"
echo "序号 | 备份目录 | 大小"
echo "----|---------|-----"

local backups=($(ls -dt $BACKUP_BASE_DIR/20* 2>/dev/null))
local count=1

for backup in "${backups[@]}"; do
if [ -d "$backup" ]; then
local backup_name=$(basename "$backup")
local backup_size=$(du -sh "$backup" 2>/dev/null | cut -f1 || echo "未知")
printf "%-4s | %-15s | %s\n" "$count" "$backup_name" "$backup_size"

# 存储备份路径
BACKUP_PATHS[$count]="$backup"
((count++))
fi
done

if [ $count -eq 1 ]; then
echo "未找到任何备份"
fi

echo ""
echo "========================================"
echo "0. 退出"
echo "========================================"
echo ""
}

# 显示备份摘要
show_backup_summary() {
local backup_path="$1"
if [ -f "$backup_path/backup_summary.txt" ]; then
cat "$backup_path/backup_summary.txt"
fi
}

# 恢复完整备份
restore_full_backup() {
local backup_path="$1"
echo ""
echo "⚠️ 警告: 这将覆盖所有现有数据库!"
read -p "确认恢复完整备份? (y/N): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
echo "开始恢复完整备份..."
zcat "$backup_path/all_databases/full_backup.sql.gz" | \
docker exec -i $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD
if [ $? -eq 0 ]; then
echo "✅ 完整备份恢复成功!"
else
echo "❌ 恢复失败!"
fi
else
echo "操作已取消"
fi
}

# 恢复单个数据库
restore_single_database() {
local backup_path="$1"

# 列出可恢复的数据库
echo ""
echo "可恢复的数据库:"

# 检查是否有单独的数据库备份
if [ ! -d "$backup_path/separate_databases" ]; then
echo "没有单独的数据库备份目录"
return
fi

# 获取数据库文件列表
local db_files=("$backup_path/separate_databases"/*.sql.gz)
if [ ${#db_files[@]} -eq 0 ] || [ ! -f "${db_files[0]}" ]; then
echo "没有单独的数据库备份文件"
return
fi

# 显示数据库列表
local db_count=1
declare -a DB_NAMES

for db_file in "${db_files[@]}"; do
if [ -f "$db_file" ]; then
local db_name=$(basename "$db_file" .sql.gz)
local size=$(du -h "$db_file" 2>/dev/null | cut -f1 || echo "未知")
echo "$db_count. $db_name ($size)"
DB_NAMES[$db_count]="$db_name"
((db_count++))
fi
done

echo ""
read -p "请选择数据库序号 (0取消): " db_choice

if [[ "$db_choice" =~ ^[0-9]+$ ]] && [ "$db_choice" -ge 1 ] && [ "$db_choice" -lt $db_count ]; then
local DB_NAME="${DB_NAMES[$db_choice]}"

echo ""
echo "⚠️ 警告: 这将覆盖数据库 $DB_NAME!"
read -p "确认恢复数据库 $DB_NAME? (y/N): " confirm

if [[ "$confirm" =~ ^[Yy]$ ]]; then
echo "开始恢复数据库 $DB_NAME..."

# 删除现有数据库
docker exec $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "DROP DATABASE IF EXISTS \`$DB_NAME\`;" 2>/dev/null

# 创建数据库
docker exec $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "CREATE DATABASE \`$DB_NAME\`;" 2>/dev/null

if [ $? -ne 0 ]; then
echo "❌ 创建数据库失败!"
return
fi

# 恢复数据库
zcat "$backup_path/separate_databases/${DB_NAME}.sql.gz" | \
docker exec -i $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD "$DB_NAME"

if [ $? -eq 0 ]; then
echo "✅ 数据库 $DB_NAME 恢复成功!"

# 验证恢复结果
echo "验证恢复结果..."
local table_count=$(docker exec $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD --skip-column-names -e "USE \`$DB_NAME\`; SHOW TABLES;" 2>/dev/null | wc -l)
if [ $? -eq 0 ]; then
echo "✅ 数据库 $DB_NAME 包含 $table_count 个表"
fi
else
echo "❌ 恢复失败!"
fi
else
echo "操作已取消"
fi
elif [ "$db_choice" = "0" ]; then
echo "操作已取消"
else
echo "无效的选择"
fi
}

# 检查容器状态
check_container() {
if ! docker ps --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
echo "❌ 错误: 容器 $CONTAINER_NAME 未运行!"
return 1
fi

# 测试 MySQL 连接
if ! docker exec $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT 1" >/dev/null 2>&1; then
echo "❌ 错误: 无法连接到 MySQL 数据库,请检查用户名和密码"
return 1
fi

return 0
}

# 主循环
main() {
# 检查容器状态
if ! check_container; then
exit 1
fi

while true; do
show_menu

read -p "请选择备份序号 (0退出): " choice

if [ "$choice" = "0" ]; then
echo "退出恢复工具"
exit 0
fi

if [ -z "${BACKUP_PATHS[$choice]}" ]; then
echo "无效的选择,请重试"
read -p "按 Enter 继续..."
continue
fi

local SELECTED_BACKUP="${BACKUP_PATHS[$choice]}"
local BACKUP_NAME=$(basename "$SELECTED_BACKUP")

echo ""
echo "已选择备份: $BACKUP_NAME"
echo ""

# 显示备份摘要
show_backup_summary "$SELECTED_BACKUP"

echo ""
echo "恢复选项:"
echo "1. 恢复完整备份 (覆盖所有数据库)"
echo "2. 恢复单个数据库"
echo "3. 返回上级菜单"
echo ""

read -p "请选择恢复选项: " restore_option

case $restore_option in
1)
restore_full_backup "$SELECTED_BACKUP"
;;
2)
restore_single_database "$SELECTED_BACKUP"
;;
3)
continue
;;
*)
echo "无效的选项"
;;
esac

echo ""
read -p "按 Enter 继续..."
done
}

# 执行主程序
main "$@"

使用方式:

1
2
3
4
5
# 1. 给执行权限
chmod +x interactive-restore.sh

# 2. 运行脚本
./interactive-restore.sh

四、参数说明与安全提示

4.1 常用参数

参数 说明 示例
-u 用户名 -u root
-p 密码(注意无空格) -p123456
-e 执行 SQL 语句 -e "SHOW DATABASES;"
-it 交互式终端 -it

4.2 安全建议

  1. 密码安全:避免在命令行明文显示密码

    1
    2
    3
    4
    5
    6
    # 方式1:手动输入(推荐)
    docker exec -it mysql-container mysql -u root -p

    # 方式2:使用环境变量
    export MYSQL_PWD=your_password
    docker exec mysql-container mysql -u root -e "SHOW DATABASES;"
  2. 容器状态:确保容器正在运行

    1
    docker ps | grep mysql
  3. 权限控制:使用最小权限账户进行操作

  4. 定期测试恢复:定期验证备份文件的可用性


五、快速测试指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 测试备份功能
docker exec mysql-container mysqldump -u root -p123456 --all-databases > /tmp/test_backup.sql

# 2. 验证备份文件
ls -lh /tmp/test_backup.sql
head -n 5 /tmp/test_backup.sql

# 3. 测试查询功能
docker exec mysql-container mysql -u root -p123456 -e "SHOW DATABASES;"

# 4. 测试恢复功能
./interactive-restore.sh

# 5. 清理测试文件
rm /tmp/test_backup.sql

六、总结与建议

核心原则:尽量不进容器

场景 推荐操作 命令示例
手动备份 宿主机直接执行 docker exec mysql容器 mysqldump -u 用户 -p密码 数据库 > 备份.sql
已进容器 备份到 /tmp/ 后复制 容器内备份 → docker cp 复制 → 清理临时文件
自动化备份 使用备份脚本 + crontab backup-mysql.sh + 定时任务
日常查看 宿主机查询 docker exec mysql容器 mysql -u 用户 -p密码 -e "SQL语句"
数据恢复 使用恢复脚本 ./interactive-restore.sh

最佳实践

  1. 自动化备份:使用提供的 backup-mysql.sh 脚本实现定时双重备份
  2. 交互式恢复:使用 interactive-restore.sh 脚本安全恢复数据
  3. 定期验证:至少每月测试一次备份文件的恢复流程
  4. 版本控制:对备份和恢复脚本进行版本管理
  5. 监控告警:监控备份任务的执行状态和磁盘空间

提示:所有命令中的容器名、用户名、密码、数据库名请替换为你的实际值。对于生产环境,建议将备份脚本化并定期测试恢复流程。

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

如何使用 Docker 快速部署 OpenClaw 客户端

发表于 2026-04-21

前言

手动安装 OpenClaw 虽然不难,但是操作比较繁琐,尤其是针对新机器,需要反复安装 Node 和 Git 环境,相当耗时,而且容易出错。

为了能以最快的速度体验上龙虾,今天我们以 Docker 的方式,来快速进行 OpenClaw 的安装。

具体部署流程

1. 创建自定义网络

1
docker network create c_bridge

创建自定义网络,方便后期多容器交互。

2. 创建数据映射目录

1
mkdir -p /root/songjian/openclaw

创建数据持久化目录,确保 OpenClaw 配置和数据不会丢失。

3. 搜索 OpenClaw 镜像

1
docker search openclaw

或访问 Docker Hub 查看可用镜像:https://hub.docker.com/r/openclaw/openclaw

4. 拉取镜像

1
2
# 拉取最新版本
docker pull ghcr.io/openclaw/openclaw:latest

镜像大小约 300MB,拉取速度取决于网络状况。

5. 创建容器

1
2
3
4
5
6
7
8
9
10
11
docker run -d \
--name=c_openclaw \
--restart=always \
--network=c_bridge \
-p 3012:3000 \
-p 18789:18789 \
-v /root/songjian/openclaw:/root/.openclaw \
-h c_openclaw \
-e TZ=Asia/Shanghai \
-e OPENCLAW_SKIP_ONBOARD=true \
ghcr.io/openclaw/openclaw:latest

参数解释:

  • -d:后台运行
  • --name=c_openclaw:指定容器名称
  • --restart=always:自动重启策略
  • --network=c_bridge:加入自定义网络
  • -p 3012:3000:将内部 3000 端口映射到宿主机 3012
  • -p 18789:18789:网关通信端口
  • -v /root/songjian/openclaw:/root/.openclaw:挂载数据目录
  • -h c_openclaw:设置容器主机名
  • -e TZ=Asia/Shanghai:设置时区
  • -e OPENCLAW_SKIP_ONBOARD=true:跳过首次引导

6. 进入容器并配置

1
2
3
4
5
6
7
8
# 查看容器是否正常运行
docker ps | grep c_openclaw

# 查看容器日志
docker logs -f c_openclaw

# 进入容器终端
docker exec -it c_openclaw /bin/sh

在容器内部执行以下命令完成初始配置:

1
2
# 运行初始化向导
openclaw onboard

7. 访问 OpenClaw

  • 管理后台:浏览器打开 http://你的服务器IP:3012
  • 网关地址:ws://你的服务器IP:18789

8. 验证服务状态

1
2
3
4
5
# 查看容器内服务状态
docker exec c_openclaw openclaw gateway status

# 检查服务健康状态
curl http://localhost:3012/health

常见问题

1. 端口冲突

如果 3012 或 18789 端口已被占用,可修改为其他端口:

1
2
# 例如使用 3013:3000
-p 3013:3000

2. 权限问题

如果遇到权限错误,可尝试修改数据目录权限:

1
sudo chmod 777 -R /root/songjian/openclaw

3. 镜像拉取失败

可尝试使用国内镜像源:

1
docker pull registry.cn-hangzhou.aliyuncs.com/openclaw/openclaw:latest

4. 配置文件丢失

所有配置文件保存在 /root/songjian/openclaw 目录,定期备份此目录即可。

管理命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 停止容器
docker stop c_openclaw

# 启动容器
docker start c_openclaw

# 重启容器
docker restart c_openclaw

# 删除容器(谨慎操作)
docker rm -f c_openclaw

# 查看容器资源使用
docker stats c_openclaw

升级到新版本

1
2
3
4
5
6
7
8
# 1. 停止并删除旧容器
docker stop c_openclaw && docker rm c_openclaw

# 2. 拉取新镜像
docker pull ghcr.io/openclaw/openclaw:latest

# 3. 使用相同命令重新创建容器
# 数据和配置会保留,因为是挂载的

总结

通过 Docker 部署 OpenClaw,你可以在 5 分钟内完成安装和配置,无需关心 Node.js 版本、系统依赖等问题。这种方式特别适合快速体验、测试环境部署

现在你已经成功部署了 OpenClaw,可以开始使用强大的 AI 智能体功能了!

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

使用 Docker-compose 部署 OpenClaw 客户端

发表于 2026-04-21

前言

之前给大家介绍了使用第三方镜像快速在 docker中部署 openclaw 客户端,详情可参见《如何使用 Docker 快速部署 OpenClaw 客户端》

今天我们换种方式,采用Docker-compose的形式来进行部署,虽然相对繁琐一些,但后期维护和迁移更加省心

这里以 Node 22 镜像为基础,复刻了官方的一键安装体验,同时预装微信和飞书插件。

具体部署操作如下:

部署流程

1. 创建自定义网络

1
docker network create c_bridge

创建自定义网络,方便后期多容器交互。

2. 创建工作目录和数据映射目录

1
2
3
4
5
6
#创建工作目录
mkdir openclaw-docker
#创建持久化数据映射目录
mkdir -p /root/openclaw
#进入工作目录
cd openclaw-docker

3. 在工作目录下创建 Dockerfile

1
vim Dockerfile

并写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FROM node:22-alpine

# 替换 Alpine 软件源加速下载
RUN sed -i 's|https://dl-cdn.alpinelinux.org|https://mirrors.aliyun.com|g' /etc/apk/repositories

# 安装git
RUN apk add --no-cache git

# 安装python3
RUN apk add --no-cache python3 py3-pip

# 配置npm国内源加速下载
RUN npm config set registry https://registry.npmmirror.com/

# 安装全局 CLI
RUN npm install -g openclaw@latest --unsafe-perm


# 跳过交互式引导
ENV OPENCLAW_SKIP_ONBOARD=true

# 声明两个默认端口
EXPOSE 18789 3000 # 3000 是 OpenClaw 内部 Web UI 端口,映射到宿主机 3012

CMD ["openclaw", "gateway", "run", "--allow-unconfigured"]

4. 在工作目录下创建 docker-compose.yml

1
vim docker-compose.yml

并写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3.8'

services:
c_openclaw:
build: .
container_name: c_openclaw
hostname: c_openclaw # 设置容器主机名
ports:
- "18789:18789" # 网关通信端口
- "3012:3000" # Web 管理界面:内部3000映射到外部3012
volumes:
- /root/openclaw:/root/.openclaw
networks:
- c_bridge
restart: always # 设置自动重启策略

networks:
c_bridge:
external: true

5. 构建并启动

1
docker compose up -d --build
  • -d:后台运行
  • --build:每次启动前重新构建镜像(确保使用 Node 22)

注意:Dockerfile 和 docker-compose.yml 必须放在同一个目录下,否则 docker compose 命令无法找到构建上下文,如下:

1
2
3
/你的路径/openclaw-docker/  # 项目根目录
├── Dockerfile
├── docker-compose.yml

如果提示 docker compose 命令不存在,则需要执行以下指令安装compose插件:

1
2
sudo apt-get update
sudo apt-get install docker-compose-plugin

6. 进入容器创建配置

  • 查看日志:

    1
    docker compose logs -f
  • 进入容器:

    1
    docker compose exec -it c_openclaw sh
  • 在容器内执行配置:
    1
    openclaw onboard

此时终端会显示配置引导,到这一步相必大家应该知道怎么做了吧

7. 安装插件(可选)

1
2
3
4
5
# 安装微信插件
npx -y @tencent-weixin/openclaw-weixin-cli@latest install

# 安装飞书插件
npx -y @larksuite/openclaw-lark install

微信二维码,用手机扫码登录即可。飞书同理,执行 npx @larksuite/openclaw-lark doctor --fix 进行配置,注意我们只需要进行配置而非安装,所以这里需要去掉-y。

8. 验证访问

  • 管理后台::浏览器打开 http://你的服务器IP:3012
  • 网关状态:检查容器内 openclaw gateway status

配置国内 Docker 镜像加速器

这是解决 Docker 下载慢最有效的方法。通过修改 Docker 的 daemon.json 配置文件,可以显著提升镜像拉取速度。

操作步骤

1. 编辑配置文件

适用于 Linux/macOS 终端或 Windows Docker Desktop 的 Docker Engine 设置:

1
2
3
4
5
6
7
8
9
10
# 创建或修改配置文件
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
]
}
EOF

2. 重启 Docker 服务

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker

3. 验证配置

运行以下命令,查看输出中是否包含你配置的镜像加速器地址:

1
docker info

在输出信息中查找 Registry Mirrors 字段,确认其中包含你配置的镜像加速器地址。

注意事项

  • 此配置只影响后续的镜像拉取操作,不会影响已下载的镜像或正在运行的容器
  • 重启 Docker 服务后,运行中的容器可能会短暂停止,但可立即重新启动
  • 如果使用 Windows 或 macOS 的 Docker Desktop,可以在设置界面直接配置镜像加速器

常见问题

  1. 容器启动失败,一直处于重启中
    通常是Dockerfile 或者docker-compose.yml文件配置有问题造成的,检查一下,修改完后执行以下命令重新构建:
    1
    2
    3
    4
    # 停止容器和服务
    docker compose down
    # 重新构建
    docker compose up -d --build

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Dockerfile 和 Docker Compose 的区别与使用指南

发表于 2026-04-21

前言

在实际项目中,我们经常需要部署包含多个组件的复杂应用,如 WordPress(需要 PHP 和 MySQL)、微服务架构等。如果使用原生的 docker run 命令逐个启动容器,不仅命令冗长,而且难以管理容器间的依赖关系和网络连接。更棘手的是,时间一久就会忘记每个容器的具体配置:映射了哪些目录、暴露了哪些端口、设置了哪些环境变量。虽然可以通过 docker inspect 查询单个容器的配置,但当容器数量增多时,这种方式效率低下,容易出错,特别是在项目迁移或团队协作时。Dockerfile 和 Docker Compose 正是为了解决这些问题而生的工具,它们让容器化应用的构建、部署和管理变得更加规范化和可维护。

Dockerfile 和 Docker Compose 的核心区别

简单来说,Dockerfile 用于定义镜像内容,而 Docker Compose 用于编排容器运行。下面是两者的详细对比:

维度 Dockerfile Docker Compose
定位 镜像构建脚本(定义如何构建镜像) 容器编排工具(定义如何运行多个容器)
作用 定义镜像内应该安装什么软件、如何配置环境 定义多个容器如何启动、互联、协作
输出 生成一个镜像文件(.tar 格式) 启动一组运行中的容器(服务栈)
文件格式 Dockerfile(纯文本指令文件) docker-compose.yml(YAML 格式配置文件)
类比 菜谱(教你如何做一道菜) 宴席订单(安排多道菜的上菜顺序和搭配)

Dockerfile:定义”一个镜像”的内容

Dockerfile 的核心就是构建镜像。它是一系列指令的集合,告诉 Docker 如何从基础镜像(如 ubuntu:22.04)开始,一步步安装依赖、复制代码、配置环境,最终生成一个可重用的镜像。

关键点:它只负责生产出静态的”安装包”(镜像),不负责运行。

Dockerfile 是一个文本文件,包含一系列指令,用于指导 Docker 如何从基础镜像开始,逐步构建出符合需求的新镜像。

核心职责

  1. 指定基础镜像(FROM)
  2. 安装软件和依赖(RUN)
  3. 复制文件(COPY/ADD)
  4. 配置环境变量(ENV)
  5. 暴露端口(EXPOSE)
  6. 定义启动命令(CMD/ENTRYPOINT)

示例:定制 WordPress 镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基于官方 WordPress 镜像
FROM wordpress:6.6-apache

# 安装系统工具
RUN apt-get update && apt-get install -y \
vim \
curl \
&& rm -rf /var/lib/apt/lists/*

# 复制自定义配置文件
COPY wp-config.php /var/www/html/

# 设置环境变量
ENV WORDPRESS_DEBUG=false

# 暴露 80 端口
EXPOSE 80

构建镜像

1
docker build -t my-wordpress:1.0 .

Docker Compose:编排”多个容器”的协作

Docker Compose 不构建镜像(除非你指定 build: 指令),它的核心是定义服务栈。它解决的是”如何让多个容器协同工作”的问题:

  • 批量操作:一条命令启动/停止所有容器(WordPress + MySQL + Redis)
  • 网络互联:自动创建网络,让容器间通过服务名直接通信(如 WordPress 直接连 mysql 主机)
  • 依赖管理:控制启动顺序(先启动数据库,再启动应用)

Docker Compose 通过一个 YAML 文件定义多容器应用的整个服务栈,包括服务间的依赖、网络、存储等配置。

示例:WordPress + MySQL 完整环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
version: '3.8'

services:
wordpress:
image: my-wordpress:1.0
ports:
- "8080:80"
environment:
- WORDPRESS_DB_HOST=mysql
- WORDPRESS_DB_USER=wpuser
volumes:
- wordpress_data:/var/www/html
depends_on:
- mysql
networks:
- app-network

mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=wordpress
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-network

volumes:
wordpress_data:
mysql_data:

networks:
app-network:
driver: bridge

启动与管理

1
2
3
4
5
6
7
8
9
10
11
# 一键启动所有服务
docker-compose up -d

# 查看本项目的容器状态
docker-compose ps

# 查看日志
docker-compose logs -f

# 停止并清理
docker-compose down

典型工作流程

在实际项目中,Dockerfile 和 Docker Compose 通常配合使用,形成完整的容器化部署流程:

1
2
3
4
5
# 1. 使用 Dockerfile 构建定制化镜像
docker build -t my-app:latest .

# 2. 使用 Docker Compose 编排多容器环境
docker-compose up -d

项目结构示例

1
2
3
4
5
6
7
my-project/
├── Dockerfile # 定义应用镜像
├── docker-compose.yml # 定义服务编排
├── src/ # 应用源代码
├── config/
│ └── wp-config.php # 配置文件
└── README.md

在这种结构中:

  • Dockerfile 确保应用能在任何地方以相同的方式运行
  • docker-compose.yml 确保多个服务能正确协同工作

常见误区澄清

  1. “Docker Compose 可以替代 Dockerfile”
    错误。Docker Compose 可以引用现有镜像,但不能定义镜像的内部构建步骤。它们解决的是不同层次的问题。

  2. “Dockerfile 也能启动多个容器”
    错误。Dockerfile 一次只构建一个镜像,不具备编排多容器的能力。启动多个容器需要多次 docker run 或使用编排工具。

  3. “必须两者都用”
    不一定。如果只运行简单的官方镜像(如单个 nginx),可以直接用 docker run,不需要 Dockerfile 或 Docker Compose。

  4. “Docker Compose 只用于开发环境”
    不完全正确。虽然 Docker Compose 在开发环境中非常方便,但它同样适用于简单的生产部署场景。对于更复杂的生产环境,通常会使用 Kubernetes 等更强大的编排工具。

如何选择?

场景 推荐方案
定制镜像需求(需安装特定软件、修改配置) 必须使用 Dockerfile
单容器运行(如简单的 Nginx 服务) 直接使用 docker run
多容器应用(如 WordPress + MySQL + Redis) 必须使用 Docker Compose
开发环境搭建(需快速启停整套服务) 强烈推荐 Docker Compose
复杂生产环境(需要高可用、自动伸缩) 考虑 Kubernetes 等高级编排工具

总结

  • Dockerfile 关注镜像内部的环境一致性,解决”这个镜像里应该装什么、怎么配置”的问题。
  • Docker Compose 关注容器之间的协作关系,解决”这些容器如何一起工作、如何通信、如何存储”的问题。

用软件开发做个类比:

  • Dockerfile ≈ 编写类的定义(Class)
  • Docker Compose ≈ 编写程序的 Main 函数,将多个类实例化并组织起来工作

理解这两者的区别和适用场景,是掌握 Docker 容器化技术的关键。在实际项目中,通常先使用 Dockerfile 定义各个服务的镜像,再通过 Docker Compose 将它们组合成一个完整的应用系统。这种分工协作的模式,让 Docker 在开发、测试、部署的全流程中都能发挥最大价值,真正实现”一次构建,到处运行”的承诺。

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

如何提升直链内容的安全性

发表于 2026-04-18

前言

在某些场景下,我们可能需要将脚本文件存放在公共仓库(如 GitHub、GitLab 等)或网盘中,并提供直链下载,以便在服务器或其他环境中快速调用。但脚本中可能会包含一些敏感信息,例如 API Token、密钥或其他凭证。

如果这些脚本内容以明文形式存放在公开可访问的位置,即使链接本身是隐蔽的,也仍存在被意外泄露的风险——例如通过仓库搜索、网盘目录遍历或 URL 被第三方获取。为了在便捷性与安全性之间取得平衡,我们可以采取一些措施对脚本内容进行保护。


解决方案

方法一:脚本加密存储

我们可以对脚本进行加密,将加密后的内容存放在公共仓库。使用时,在服务器环境中通过命令行工具(如 openssl)解密后执行。

示例步骤:

  1. 原始脚本内容(假设其中包含一个 GitHub Token):
1
2
3
curl -H "Authorization: token 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" \
-H "Accept: application/vnd.github.v3.raw" \
https://api.github.com/repos/youban/openclaw/contents/restore.sh | bash
  1. 加密脚本(使用 AES-256-CBC 加密,并输出为 base64 格式):
1
2
3
4
openssl enc -aes-256-cbc -a -salt -pbkdf2 \
-in original_script.sh \
-out encrypted_script.enc \
-pass pass:你的密码

加密后得到类似以下字符串(示例):

1
U2FsdGVkX18qJ6tY1L2v8QkQ7wK1M9pN3cR7fD2hG5jL8mZ4vC9xP6bT1aE3sW0y
  1. 将加密文件上传至公共仓库,例如 GitHub Raw URL:
1
https://raw.githubusercontent.com/用户名/仓库名/main/encrypted_script.enc
  1. 在服务器终端执行以下命令:
1
2
3
curl -fsSL "https://raw.githubusercontent.com/用户名/仓库名/main/encrypted_script.enc" | \
openssl enc -aes-256-cbc -a -d -pbkdf2 -pass pass:你的密码 | \
bash

优点:

  • 内容经过强加密,即便链接公开,没有密码也无法解密
  • 无需在脚本中硬编码密钥

注意事项:

  • 需自行保管好解密密码
  • 执行时需在命令行中传入密码(也可通过环境变量等方式传递,避免在历史记录中暴露)

方法二:使用 GitHub Gist 存储

如果脚本内容并非高度敏感,但仍不希望被公开搜索引擎收录或随意访问,可以使用 GitHub Gist 进行存储。Gist 可以设为”公开”或”秘密”,后者不会出现在搜索结果中,但仍可通过链接访问。

使用方法:

  1. 在 Gist 中创建脚本文件,例如 restore.sh。
  2. 获取该文件的 Raw URL,格式一般为:
1
https://gist.githubusercontent.com/你的用户名/Gist_ID/raw/文件名
  1. 在命令行中直接执行:
1
curl -fsSL "https://gist.githubusercontent.com/你的用户名/Gist_ID/raw/restore.sh" | bash

特点:

  • 链接简短,易于管理
  • 可通过”秘密”状态避免被公开搜索
  • 无需额外解密步骤,适合不包含敏感信息的脚本

总结

方案 适用场景 优点 注意事项
加密存储 含密钥、Token 等敏感信息 强加密,链接泄露仍安全 需保管密码,执行时需解密
Gist 存储 非敏感脚本,但不愿公开被爬取 简单快捷,链接隐秘 非加密,仅依赖链接保密

选择哪种方案可根据脚本内容的敏感程度、使用便利性与安全需求综合决定。如对安全性有更高要求,还可结合访问令牌、IP 白名单、临时链接等机制进一步增强保护。

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

12…51

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

507 日志
145 标签
RSS
© 2026 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%