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

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


  • 首页

  • 归档

  • 搜索

如何配置 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 白名单、临时链接等机制进一步增强保护。

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

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

如何配置 OpenClaw 自动切换大模型

发表于 2026-04-17

前言

我目前主要使用免费大模型训练智能体,免费模型每天都有 API 调用额度限制,我希望其中一个模型额度用尽时可以自动切换模型。

很方便的是,OpenClaw 原生就支持额度耗尽自动降级。你只需要配置好 fallbacks(回退模型链),系统会在检测到 429、额度不足或 API 错误时自动切换。

基础配置

编辑配置文件 ~/.openclaw/config.json5,设置主模型和备用模型顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"agents": {
"defaults": {
"model": {
"primary": "openai/gpt-4o", // 主模型
"fallbacks": [
"openai/gpt-4o-mini", // 备用1
"google/gemini-2.0-flash" // 备用2
],
"fallbackPolicy": {
"maxRetries": 2, // 重试次数
"notifyOnFallback": true // 切换时通知
}
}
}
}
}

触发条件:当主模型返回 insufficient_quota(额度用尽)、rate_limit(限频)或网络超时时,会自动按顺序尝试 fallbacks 中的模型。

进阶配置:多 Key 轮换(防限流)

如果你有多个同款模型的 API Key,可以在同一模型下配置多个认证(auth profiles),系统会优先在内部轮换 Key,实在不行才切模型:

1
2
3
# 添加多个 OpenAI Key(同模型不同账号)
openclaw auth add openai --name key2
openclaw auth add openai --name key3

策略:Key1 被限流 → 自动用 Key2 → Key3 → 都不可用 → 切到 fallback 模型。

验证配置

配置完成后,执行以下命令查看状态:

1
2
openclaw models status      # 查看当前模型链
openclaw models list # 查看已配置的模型

建议:把你的主力模型(如 GPT-4o)设为主模型,把便宜/免费的模型(如 GPT-4o-mini、Gemini Flash)设为 fallback,这样既能保证质量,又能在额度见底时无缝降级。

兜底方案:之 GIT 同步

假设 openclaw 部署在第三方云端机器上,你无法通过终端控制远程机器,只能利用 openclaw 来操控服务器的所有配置。

为了防止不小心将所有模型的额度都用尽导致无法与服务器建立联系,此时我们需要一个兜底方案,那就是将配置文件乃至整个 openclaw 目录交由 Git 仓库托管,当极端情况出现的时候,我们可以在本地修改配置文件然后同步到云端服务器,从而恢复与龙虾的对话。

  1. 首先在 GitHub 或者 Gitee 上创建一个新仓库。
  2. 然后在大模型额度用尽前让智能体把需要同步的目录上传到新仓库中,同时创建定时任务,每分钟从 Git 仓库拉取最新文件:
1
2
3
4
# 编辑 crontab
crontab -e
# 添加一行(每分钟检查一次)
* * * * * cd /home/gem/workspace/agent/workspace && git pull -q origin main

在这之前需要生成专属服务器的 SSH 密钥,方便服务器对该仓库的读写操作,这一步也可以交给智能体来做。

  1. 然后在手机或者自己的电脑也安装相应的 git 工具,把仓库克隆下来,方便我们随时修改。如果你想在苹果手机上使用 GIT,推荐大家尝试一下 Working Copy 这个应用,可以免费管理一个 GIT 仓库,足够我们使用了。

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

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

openclaw 连接飞书机器人完整指南

发表于 2026-04-17

文档概述

本文档提供 OpenClaw 与飞书机器人集成的完整配置指南,包含本地部署、云端部署、问题排查及高级配置等全流程说明。


一、方案选择与对比

1.1 方案对比表

特性 官方飞书插件 社区版飞书插件
安装方式 需单独安装 内置,无需安装
功能范围 ✅ 完整飞书生态功能
• 基础聊天
• 文档/表格操作
• 日历管理
• 多维表格
✅ 基础功能
• 文档聊天
• 多智能体协作
多智能体协作 ✅ 支持 ✅ 支持
配置复杂度 中等 简单
适用场景 企业级应用、需操作飞书内容 快速测试、纯对话场景
版本要求 OpenClaw v0.8.0+ OpenClaw 任何版本

1.2 快速选择指南

1
2
3
4
# 如果你需要:
# 1. 仅基础聊天 → 用社区版
# 2. 操作飞书文档/日历 → 用官方插件
# 3. 不确定需求 → 从社区版开始,需要时升级

二、前置准备

2.1 环境检查

确保满足以下条件:

  • ✅ OpenClaw 已正确安装并可运行
  • ✅ 网络通畅,可访问飞书服务器
  • ✅ 拥有飞书管理员权限(创建应用需要)

2.2 飞书账号权限

权限类型 必需等级 用途说明
开发者权限 必需 创建机器人应用
应用发布权限 必需 将机器人发布到企业
安全审核权限 推荐 管理应用安全设置

三、本地部署配置(扫码方式)

3.1 安装官方插件

1
2
3
4
5
# 通过 npm 安装最新版
npx -y @larksuite/openclaw-lark install

# 或指定版本安装
npx -y @larksuite/openclaw-lark@1.2.0 install

3.2 扫码绑定流程

  1. 执行安装命令后,会显示如下信息:

    1
    2
    3
    4
    5
    6
    🔍 正在初始化飞书插件...
    📱 请使用飞书扫描下方二维码完成绑定:
    ████████████████
    ████████████████
    ████████████████
    ████████████████
  2. 使用飞书APP扫码:

    • 确保登录正确的飞书账号
    • 扫码后自动创建机器人应用
    • 自动完成权限配置
  3. 绑定成功提示:

    1
    2
    3
    4
    ✅ 飞书通道连接成功!
    机器人名称: OpenClaw助手
    企业: Your Company
    绑定时间: 2024-01-01 10:00:00

3.3 验证本地连接

1
2
3
4
5
# 验证连接状态
openclaw channels status --channel feishu

# 测试发送消息
openclaw feishu test "Hello, Feishu!"

四、云端部署配置(手动方式)

4.1 创建飞书机器人应用

方式一:快速创建(推荐)

访问专属链接:open.feishu.cn/page/openclaw

步骤:

  1. 使用企业管理员账号登录
  2. 点击”一键创建”
  3. 自动生成包含所有必需权限的应用
  4. 保存生成的 App ID 和 App Secret

方式二:手动创建

  1. 访问飞书开放平台:

    1
    https://open.feishu.cn/app
  2. 创建企业自建应用:

    • 应用名称:OpenClaw-Bot
    • 应用描述:智能助手机器人
    • 权限范围:选择”企业内部使用”
  3. 配置机器人能力:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 必需权限列表:
    权限名称: 获取应用信息
    权限范围: 全部
    状态: 开启

    权限名称: 获取用户基础信息
    权限范围: 全部
    状态: 开启

    权限名称: 获取用户邮箱信息
    权限范围: 全部
    状态: 开启

    权限名称: 获取用户user_id
    权限范围: 全部
    状态: 开启

    权限名称: 以应用身份读取通讯录
    权限范围: 全部
    状态: 开启

    权限名称: 获取与发送单聊、群组消息
    权限范围: 全部
    状态: 开启
  4. 发布应用:

    • 版本管理 → 创建版本
    • 申请发布 → 等待管理员审批
    • 审批通过后获取凭证

4.2 获取应用凭证

  1. 进入应用后台:

    1
    开发者后台 → 应用功能 → 凭证与基础信息
  2. 获取关键信息:

    1
    2
    3
    4
    5
    6
    7
    # 必需信息
    App ID = cli_xxxxx
    App Secret = xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

    # 可选:加密密钥
    Encrypt Key = (如有)
    Verification Token = (如有)
  3. 安全建议:

    1
    2
    3
    4
    # 将凭证保存在安全位置
    # 避免提交到版本控制系统
    export FEISHU_APP_ID="cli_xxxxx"
    export FEISHU_APP_SECRET="your_secret_here"

4.3 安装并配置插件

安装插件

1
2
3
4
5
# 如果尚未安装官方插件
npx -y @larksuite/openclaw-lark install

# 或通过 OpenClaw 安装
openclaw plugins install @larksuite/openclaw-lark

配置凭证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法1:通过配置文件
# 编辑 ~/.openclaw/config.yaml
channels:
feishu:
app_id: "cli_xxxxx"
app_secret: "your_secret_here"
verification_token: "" # 可选
encrypt_key: "" # 可选

# 方法2:通过环境变量
export FEISHU_APP_ID="cli_xxxxx"
export FEISHU_APP_SECRET="your_secret_here"
export FEISHU_VERIFICATION_TOKEN="your_token"
export FEISHU_ENCRYPT_KEY="your_encrypt_key"

启用插件

1
2
3
4
5
# 启用飞书通道
openclaw channels enable feishu

# 重启 OpenClaw 服务
openclaw restart

4.4 配对验证

获取配对码

  1. 进入飞书,找到刚创建的机器人

  2. 发送任意消息,例如:

    1
    2
    3
    hi
    你好
    test
  3. 机器人回复示例:

    1
    2
    3
    4
    5
    6
    7
    8
    🤖 OpenClaw: 访问未配置

    配对信息:
    用户ID: ou_4cfc4b4ed6334283a697bfe77a8cf2xx
    配对码: 7B9K

    请管理员执行以下命令完成配对:
    openclaw pairing approve feishu 7B9K

完成配对

  1. 在 OpenClaw 终端执行:

    1
    2
    # 复制上一步获取的命令
    openclaw pairing approve feishu 7B9K
  2. 成功响应:

    1
    2
    3
    4
    ✅ 配对成功!
    用户: 张三 (zhangsan@company.com)
    时间: 2024-01-01 10:00:00
    权限: 完全访问
  1. 返回飞书验证:
    1
    2
    3
    # 发送测试消息
    /help
    # 应该能收到 OpenClaw 的帮助信息

五、社区版插件使用

5.1 社区版配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 清空旧配置(如有)
openclaw config unset channels.feishu

# 2. 触发登录向导
openclaw channels login --channel feishu

# 3. 按照提示输入凭证
? 请输入 Feishu App ID: cli_xxxxx
? 请输入 Feishu App Secret: ********
? 是否需要加密验证 [y/N]: N

# 4. 验证状态
openclaw channels status --channel feishu
# 期望输出: Status: connected

5.2 版本检测

1
2
3
4
5
6
7
# 在飞书机器人对话框输入
/feishu version

# 或输入
/feishu auth
# 官方插件: 显示授权界面
# 社区插件: 提示"命令未找到"

六、连接状态检查

6.1 基础检查命令

1
2
3
4
5
6
7
8
# 查看所有通道状态
openclaw channels list

# 查看飞书通道详细状态
openclaw channels status --channel feishu --verbose

# 测试消息发送
openclaw feishu test "连接测试"

6.2 状态诊断表

状态 含义 解决方案
connected ✅ 连接正常 无需操作
disconnected ❌ 连接断开 检查凭证/网络
connecting 🔄 连接中 等待10-30秒
error ⚠️ 错误状态 查看详细日志
not_configured ⚠️ 未配置 完成配置步骤

6.3 日志检查

1
2
3
4
5
6
7
8
9
# 查看详细连接日志
openclaw --debug channels status --channel feishu

# 实时查看日志
tail -f ~/.openclaw/logs/feishu.log

# 常见日志位置
# Linux/macOS: ~/.openclaw/logs/
# Windows: %APPDATA%\.openclaw\logs\

七、常见问题排查

7.1 连接问题

问题:收不到配对码

可能原因及解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 检查应用是否已发布
# 访问飞书开发者后台 → 应用发布状态
# 确保状态为"已启用"

# 2. 检查网络连接
ping open.feishu.cn
# 应能正常ping通

# 3. 重置配置
openclaw config unset channels.feishu
openclaw config set channels.feishu.app_id "你的AppID"
openclaw config set channels.feishu.app_secret "你的Secret"
openclaw channels restart feishu

问题:配对码无效/过期

解决步骤:

  1. 配对码有效期为 5分钟
  2. 重新在飞书发送消息获取新配对码
  3. 确保执行配对命令的 OpenClaw 实例是同一个

7.2 权限问题

权限检查清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 必需权限配置
required_permissions:
- contact:user.basic:readall # 读取用户基础信息
- contact:user.employee_id:readall # 读取员工号
- contact:user.email:readall # 读取用户邮箱
- im:message:send_as_bot # 发送消息
- im:message:send # 接收消息
- im:message:read_users # 读取用户消息

# 按需开启权限
optional_permissions:
- drive:drive:readonly # 云文档读取
- calendar:calendar:readonly # 日历读取
- sheets:spreadsheet:readonly # 表格读取

7.3 消息问题

机器人能收不能发

检查步骤:

1
2
3
4
5
6
7
8
# 1. 检查机器人是否加入会话
# 飞书要求机器人必须被@或加入群聊才能主动发消息

# 2. 检查消息频率限制
# 飞书限制:单聊 5条/秒,群聊 20条/秒

# 3. 检查内容安全
# 部分内容可能被飞书安全策略拦截

消息延迟高

优化建议:

1
2
3
4
# 调整消息处理参数
openclaw config set channels.feishu.concurrency 10
openclaw config set channels.feishu.timeout 30
openclaw config set channels.feishu.retry_attempts 3

八、高级配置

8.1 多环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 配置文件示例:~/.openclaw/config.yaml
channels:
feishu:
# 生产环境
production:
app_id: "cli_prod_xxxx"
app_secret: "${FEISHU_PROD_SECRET}"
enabled: true

# 测试环境
staging:
app_id: "cli_test_xxxx"
app_secret: "${FEISHU_TEST_SECRET}"
enabled: false

# 开发环境
development:
app_id: "cli_dev_xxxx"
app_secret: "${FEISHU_DEV_SECRET}"
enabled: false

8.2 安全增强配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
security:
# 启用消息加密
encryption: true
encrypt_key: "${FEISHU_ENCRYPT_KEY}"

# IP白名单
ip_whitelist:
- "127.0.0.1"
- "192.168.1.0/24"

# 访问令牌刷新
token_refresh: 7200 # 2小时刷新一次

# 审计日志
audit_log: true
log_path: "/var/log/openclaw/audit.log"

8.3 性能调优

1
2
3
4
5
6
7
8
9
10
11
12
13
# 调整并发数
openclaw config set channels.feishu.concurrency 20

# 设置超时时间
openclaw config set channels.feishu.timeout 60

# 启用消息缓存
openclaw config set channels.feishu.cache.enabled true
openclaw config set channels.feishu.cache.ttl 300

# 连接池配置
openclaw config set channels.feishu.connection_pool.min 5
openclaw config set channels.feishu.connection_pool.max 50

8.4 监控与告警

1
2
3
4
5
6
7
8
9
# 启用健康检查
openclaw config set channels.feishu.health_check.enabled true
openclaw config set channels.feishu.health_check.interval 30

# 配置告警
openclaw config set channels.feishu.alerts.enabled true
openclaw config set channels.feishu.alerts.webhook "https://hooks.slack.com/..."
openclaw config set channels.feishu.alerts.thresholds.error_rate 0.1
openclaw config set channels.feishu.alerts.thresholds.latency 1000

九、故障排查流程图

graph TD
    A[连接出现问题] --> B{能收到配对码吗?}

    B -->|能| C[执行配对命令]
    C --> D[配对成功?]
    D -->|是| E[✅ 连接成功]
    D -->|否| F[检查网络连接]
    F --> G[网络正常?]
    G -->|是| H[检查凭证有效期]
    G -->|否| I[修复网络问题]
    H --> J[凭证有效?]
    J -->|是| K[检查飞书应用状态]
    J -->|否| L[重新获取凭证]

    B -->|不能| M{终端显示二维码?}
    M -->|是| N[用飞书扫码]
    M -->|否| O[检查App ID/Secret]
    O --> P[凭证正确?]
    P -->|是| Q[检查应用权限]
    P -->|否| R[重新获取凭证]

    N --> E
    R --> S[重新配置]
    S --> B

    K --> T[应用已发布?]
    T -->|是| U[检查机器人是否在会话中]
    T -->|否| V[发布应用]
    U --> W[在会话中?]
    W -->|是| X[查看详细错误日志]
    W -->|否| Y[将机器人加入会话]

    X --> Z[根据错误码处理]

    classDef success fill:#d4edda,stroke:#155724
    classDef warning fill:#fff3cd,stroke:#856404
    classDef danger fill:#f8d7da,stroke:#721c24
    classDef info fill:#d1ecf1,stroke:#0c5460

    class E success
    class F,G,H,I,J,K,L warning
    class M,N,O,P,Q,R danger
    class S,T,U,V,W,X,Y,Z info

十、附录

10.1 错误代码表

错误码 含义 解决方案
99991661 应用未启用 在飞书后台发布应用
99991664 权限不足 检查并添加对应权限
99991668 参数错误 检查 App ID/Secret 格式
99991669 请求过于频繁 降低请求频率
99991672 用户不在会话中 将机器人加入会话

10.2 相关命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基础命令
openclaw channels list # 列出所有通道
openclaw channels enable feishu # 启用飞书通道
openclaw channels disable feishu # 禁用飞书通道
openclaw channels restart feishu # 重启飞书通道

# 诊断命令
openclaw feishu debug # 调试模式
openclaw feishu test "消息" # 测试消息
openclaw feishu webhook info # 查看webhook信息

# 配置命令
openclaw config get channels.feishu # 查看飞书配置
openclaw config set channels.feishu.app_id "xxx" # 设置App ID
openclaw config unset channels.feishu # 清除配置

10.3 资源链接

  • 飞书开放平台
  • OpenClaw 官方文档
  • 飞书插件 GitHub
  • 问题反馈
  • 社区支持

10.4 联系支持

问题类型 联系方式 响应时间
配置问题 GitHub Issues 1-2工作日
紧急故障 飞书支持群 2-4小时
功能建议 社区论坛 3-5工作日
企业支持 商务合作 1工作日

文档版本

  • 版本:v2.1
  • 更新日期:2024-12-17
  • 适用版本:OpenClaw v0.8.0+
  • 飞书插件版本:v1.2.0+

更新日志

日期 版本 更新内容
2024-12-17 v2.1 新增故障排查流程图,优化配置示例
2024-12-10 v2.0 重构文档结构,增加高级配置章节
2024-12-01 v1.0 初始版本发布

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

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

12…51

乱码三千

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

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