Carry の Blog Carry の Blog
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • MySQL实战45讲学习笔记
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carry の Blog

好记性不如烂键盘
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • MySQL实战45讲学习笔记
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 工作笔记

  • K8S

  • Nginx

  • 监控

  • 网络安全

  • 其他

  • Docker

    • Docker使用笔记:容器保活、快速起 MySQL、密码管理器部署三件事
      • 1. 容器为什么会"跑着跑着就退出了"
      • 2. 两种保活方式,怎么选
      • 3. 最简单起一个 MySQL 容器
      • 4. 用密码管理器(Vaultwarden)验证"数据库分离部署"模式
      • 5. 可复用要点
    • Browserless 使用笔记:把 Chrome 变成一个可以被并发调用的网络服务
  • Linux笔记
  • Docker
Carry の Blog
2022-03-10
目录

Docker使用笔记:容器保活、快速起 MySQL、密码管理器部署三件事

版本说明

本文命令在 Docker 24.x / 25.x 下验证通过,docker run 参数多年未变,可放心用于任意近期版本。

# 1. 容器为什么会"跑着跑着就退出了"

刚上手 Docker 最常踩的坑:docker run 起一个基础镜像(比如 alpine、ubuntu)想进去调试,结果容器秒退。原因是 Docker 容器的生命周期绑定的是容器内 PID 1 进程——PID 1 退出,容器立刻停止,不管你有没有 -d。基础镜像的默认命令往往是一次性任务(比如打印完就退出的 shell),没有一个"一直不退出"的前台进程。

# 2. 两种保活方式,怎么选

给容器一个不会退出的 PID 1,常见两种写法:

# 方式一:sleep infinity
docker run -d IMAGE sleep infinity

# 方式二:tail -f /dev/null
docker run -d IMAGE tail -f /dev/null
1
2
3
4
5

infinity 是 GNU coreutils 的 sleep 特有参数,表示"睡到天荒地老",不会像 sleep 999999 那样最终到期。tail -f /dev/null 则是监听一个永远不会有新内容的空设备,效果等价,但 tail 在 Alpine 精简镜像(busybox 版)里也保证存在,兼容性略好。

预期输出:起完用 docker ps 能看到容器 STATUS 是 Up,而不是几秒前的 Exited (0)。

为什么不用 docker run -d IMAGE(不加保活命令)直接期待它常驻:如果镜像本身没有定义长驻的 CMD/ENTRYPOINT(比如纯工具镜像),容器会在默认命令跑完后立刻退出,这不是 bug,是设计如此——Docker 认为"容器 = 一个进程的生命周期",不是"容器 = 一台常开的虚拟机"。需要常驻调试环境时才需要手动补一个保活命令。

坑:sleep infinity / tail -f /dev/null 本身几乎不占资源(休眠/阻塞态),但如果你开了几十个这样的"僵尸调试容器"忘记清理,累积的容器元数据和日志文件依然会占磁盘。定期 docker ps -a --filter status=exited + docker container prune 清理。

# 3. 最简单起一个 MySQL 容器

不装机器上的 MySQL,直接拉镜像跑起来,用于本地开发或临时验证:

docker run --name some-mysql \
  -e MYSQL_ROOT_PASSWORD=<PASSWORD> \
  -v /docker/mysql/conf:/etc/mysql/conf.d \
  -v /docker/mysql/logs:/logs \
  -v /docker/mysql/data:/var/lib/mysql \
  -p 3306:3306 \
  -d mysql:latest
1
2
3
4
5
6
7

三个挂载对应配置、日志、数据三类文件,目的是让容器可以随意删掉重建而不丢数据——这是容器化部署有状态服务的核心原则:容器本身是一次性的,状态必须落在宿主机的卷上。

验证:

docker logs some-mysql --tail 20   # 看到 "ready for connections" 才算真正起来
docker exec -it some-mysql mysql -uroot -p<PASSWORD> -e "select 1"
1
2

坑:mysql:latest 会随时间指向不同大版本(比如某天从 8.0 跳到 8.4),生产环境务必锁定具体 tag(如 mysql:8.0.34),否则重建容器时可能因为大版本跳变导致数据目录不兼容、容器起不来。

# 4. 用密码管理器(Vaultwarden)验证"数据库分离部署"模式

在真实场景中会更进一步:容器只跑应用本身,数据库是外部独立服务(自建或云数据库),这样应用容器可以随意重建、扩缩容,数据库单独维护备份策略。以开源密码管理器 Vaultwarden(Bitwarden 兼容后端)为例:

docker run -d --name vaultwarden \
  -e DATABASE_URL='mysql://user:password@db-host:3306/vaultwarden' \
  -p 8088:80 \
  -v /data/vaultwarden:/data/ \
  vaultwarden/server:1.25.1
1
2
3
4
5

这里 DATABASE_URL 指向的是一个外部 MySQL(可以就是上一节起的那个容器,也可以是独立数据库主机),-v /data/vaultwarden:/data/ 只保留应用侧的附件/图标缓存这类非核心数据。这种"应用容器 + 外部数据库"的组合是容器化部署最常见的模式:应用层无状态、可随时 docker rm 重建;数据层有状态、独立管理生命周期。

为什么锁定镜像版本号(1.25.1 而不是 latest):密码管理器这类涉及加密格式的服务,大版本升级可能伴随数据库 schema 变更,锁版本号能保证"这次部署跑的就是我验证过的那个版本",升级是主动决策而不是意外发生。

验证:访问 http://<host>:8088 能看到 Vaultwarden 登录页;docker logs vaultwarden 里出现数据库连接成功且无 migration 报错。

# 5. 可复用要点

  1. 容器退出往往不是故障,是 PID 1 进程正常退出——需要常驻就显式给一个不退出的前台命令(sleep infinity / tail -f /dev/null)。
  2. 任何有状态服务(数据库、密码管理器)都用 -v 把状态挂到宿主机卷,容器本身当一次性资源对待。
  3. 生产环境永远锁定镜像 tag 到具体版本号,latest 只适合临时验证。
  4. "应用容器 + 外部数据库"是比"全部塞进一个容器"更容易维护和扩展的默认模式。
#Docker#容器
上次更新: 8/7/2026

← nginx的webdav设置 Browserless 使用笔记:把 Chrome 变成一个可以被并发调用的网络服务→

最近更新
01
Browserless 使用笔记:把 Chrome 变成一个可以被并发调用的网络服务
08-07
02
ORDER BY 配合 LIMIT 触发的索引选择陷阱 原创
08-07
03
Nginx 运维知识地图:从配置基础到反向代理实战 原创
07-29
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式