凌晨 3 点的告警

凌晨 3 点,监控大屏突然变红。磁盘使用率:100%。

连上服务器排查了一圈,发现罪魁祸首是一个才跑了 3 天的 Docker 容器——日志文件撑到了 40GB。再一看启动命令,docker run 后面只跟了 -d -p 8080:80 nginx,什么都没限制。

这大概是 Docker 使用中最常见的一种死法:命令写得简单,代价是上线之后不知道什么时候爆雷。

docker run 是 Docker 里最核心的命令,参数有七八十个。大多数人入门时只学了最基础那几个,剩下的要么从文档里零散查到,要么等踩坑了才去翻。这篇文章把这些参数按实战场景重新组织一遍,配合图表和避坑提示,争取让每一次敲下 docker run 时都有一个清醒的判断。


参数总览

先上一张总图,建立全局感:

docker run

基础运行

-d / -it / --rm

--name / --hostname

-e / --env-file

资源约束

-m / --memory-swap

--cpus / --cpu-shares

网络配置

--network

--add-host / --dns

存储挂载

--tmpfs

-v / --mount

安全加固

-u / --read-only

--cap-drop / --cap-add

--security-opt

--privileged

--health-cmd

运行时行为

--restart

--log-driver / --log-opt

--entrypoint / CMD


场景一:给容器一个正经名字

--name

docker run -d --name my-nginx nginx

不指定名字时,Docker 会随机生成一个,比如 stoic_lovelacevibrant_wozniak。查日志、进入容器、链接容器的时候,全靠猜,非常影响排查效率。

生产环境里,命名规范比想象中重要。建议统一格式,例如:${服务名}-${环境}-${序号},像 api-prod-01 这样,一眼能认出是谁。

--hostname

docker run -d --hostname app-server nginx

这只影响容器内部的 hostname,会写入 /etc/hostname。在微服务互相通信的场景里,hostname 能让日志的归属更清晰——一条来自 app-server 的报错比来自一串容器 ID 友好得多。


场景二:环境变量怎么传

-e / --env

docker run -e DB_HOST=localhost -e DB_PORT=5432 myapp

容器内进程通过 getenv()os.environ 读取这些值。适合少量配置项。

注意:环境变量是明文传递的,不适合放密码。密码相关的内容走 Docker Secrets,或者挂载只读配置文件。

--env-file

docker run --env-file .env myapp

参数一多,逐个 -e 就显得很乱。.env 文件格式清晰,维护起来方便得多:

DB_HOST=localhost
DB_PORT=5432
APP_ENV=production

场景三:资源限制——不配这个迟早出事

先看一张资源限制体系图:

CPU限制

--cpus
绝对上限(核数)

--cpu-shares
相对权重

内存限制

-m / --memory
内存上限

--memory-swap
总上限(含swap)

-m / --memory

docker run -m 512m myapp

限制容器最大使用 512MB 内存。单位支持 bkmg

突破内存上限时,容器进程会被 Linux OOM Killer 直接杀掉,进程退出码 137,属于非优雅终止。对于 Java、Node.js 这类预分配堆内存的应用,要留足够的余量,比如一个常驻 256MB 的服务,上限设到 512m 比较稳妥。

--memory-swap

docker run -m 512m --memory-swap 1g myapp

这里 --memory-swap 表示内存加 swap 的总上限,不是 swap 单独的大小。上例中物理内存 512MB,swap 可用 512MB,加起来 1GB。

如果设成和 -m 相同值,则 swap 完全禁用。

--cpus

docker run --cpus 1.5 myapp

容器最多能占用 1.5 个 CPU 核,属于绝对上限。可以是小数。

--cpu-shares

docker run --cpu-shares 512 myapp

这是相对权重,默认为 1024。当 CPU 资源紧张时,按权重比例分配空闲 CPU 时间。设成 512 意味着只有其他容器设默认值 1024 时的一半。

重要:CPU 空闲时不受限制,没有"只能用一半"的说法,所以这个参数在资源充足的机器上基本看不出效果。


场景四:网络怎么配通

Docker网络类型

IP手动管理
服务发现困难

服务名直接互通
推荐生产使用

最高性能
端口冲突风险

完全隔离

bridge
默认bridge网络

自定义bridge
用户创建

host
复用宿主机网络栈

none
无网络

--network

# 加入自定义网络(推荐)
docker run --network my-custom-net myapp

# 使用宿主机网络栈
docker run --network host myapp

# 无网络
docker run --network none myapp

自定义 bridge 网络下,容器之间可以直接用容器名做 DNS 解析,不需要记 IP。生产环境强烈推荐。

host 模式跳过 Docker 的网络 NAT 层,性能最好,但端口要自己管理——两个容器同时用 80 端口就直接冲突了。

--add-host

docker run --add-host db-server:192.168.1.100 myapp

往容器内的 /etc/hosts 添加一条静态映射。本地开发连内网数据库、又不想动系统 hosts 文件的时候,这个参数很顺手。

--dns

docker run --dns 8.8.8.8 --dns 114.114.114.114 myapp

覆盖容器内的 DNS 服务器。有些镜像默认 DNS 配置不合理,解析慢甚至超时,加上这个就能解决。


场景五:重启策略——别让服务器半夜 call 你

手动 docker stop

OOM / 异常

no

on-failure

always

unless-stopped

容器退出

退出原因?

no / unless-stopped
不重启

重启策略?

不重启

退出码≠0时重启
可加次数限制

无条件重启

重启除非
手动stop过

--restart

# 不自动重启(默认)
docker run --restart no myapp

# 异常退出时重启,可选重试上限
docker run --restart on-failure:5 myapp

# 总是重启(包括手动 stop 后)
docker run --restart always myapp

# 总是重启,但手动 stop 后不再自动拉起
docker run --restart unless-stopped myapp

unless-stopped 是生产环境最常用的选择。服务器重启后容器自动拉起,正常维护时运维手动停了容器不会被再次自动启动。always 适合那些宁可多重启也不能宕机的场景。

on-failure 常用于批处理任务:失败了自动重试几次,成功跑完就直接退出。


场景六:用户权限——别让容器以 root 身份乱跑

-u / --user

docker run -u 1000:1000 myapp
docker run -u nobody myapp

大多数镜像默认以 root 运行。生产环境这是大忌——容器逃逸漏洞一旦被利用,宿主机也跟着完蛋。

指定 UID/GID 运行,能在大多数场景下把风险降一档。如果挂载目录后报权限错误,通常就是容器进程的用户和宿主机目录的属主不匹配导致的。

--read-only

docker run --read-only myapp

把容器的根文件系统设为只读。进程写不进去,自然不会被植入恶意文件。应用如果需要写临时文件,配合 --tmpfs/tmp 分配一块内存盘:

docker run --read-only --tmpfs /tmp myapp

场景七:挂载——数据放哪里

--mount(推荐写法)

-v 简单但参数顺序容易搞混。--mount 更显式,格式也更适合自动化:

# 绑定挂载宿主机目录
docker run --mount type=bind,source=/host/data,target=/app/data myapp

# 使用命名卷(Docker 管理存储)
docker run --mount type=volume,source=myvolume,target=/app/data myapp

# 内存临时文件系统
docker run --mount type=tmpfs,target=/tmp,tmpfs-size=100m myapp

# 只读挂载
docker run --mount type=bind,source=/config,target=/etc/app,readonly myapp

命名卷由 Docker 统一管理,备份和迁移都比较方便。绑定挂载直接暴露宿主机路径,权限控制更灵活。

--tmpfs

docker run --tmpfs /tmp:size=100m,mode=1777 myapp

tmpfs 放在内存里,容器停止后内容消失。适合存会话 token、缓存、临时文件这类不需要持久化的东西。速度比磁盘快很多。


场景八:日志——40GB 的教训

日志配置三件套

--log-driver
选择存储后端

--log-opt max-size
单个文件大小上限

--log-opt max-file
保留文件数量

默认日志驱动是 json-file,存在 /var/lib/docker/containers/<container-id>/ 下,没有大小限制。上线三个月磁盘悄悄满了,根因往往就是这个。

docker run \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp

每个日志文件最大 10MB,保留 3 个文件,总共 30MB 上限。上线前加上这个,能省掉无数次的"磁盘满了"告警。

其他日志驱动:

驱动 说明
syslog 转发到系统 syslog
journald 写入 systemd journal
gelf 发给 Graylog / ELK
none 完全不记录日志

场景九:交互——进入容器调试

-i / -t / -it

# 交互式 shell
docker run -it ubuntu bash

# 非交互执行一条命令
docker run ubuntu echo "hello"

-i 保持标准输入连接,-t 分配一个伪终端。需要敲命令交互就 -it,纯取输出不需要加。进了容器之后想退出但不杀进程,按 Ctrl+P+Q


场景十:退出即删

--rm

docker run --rm ubuntu echo "done"

容器退出后自动删除,临时任务和 CI 流水线里很常用,省得手动的去清理。需要注意的是:--rm--restart 互斥,不可能同时既是自动重启又是自动删除。


场景十一:安全加固四件套

最小权限原则

-u 指定非root用户

--cap-drop ALL

按需加回所需capabilities

--read-only

--security-opt
微调seccomp/AppArmor

按需启用

--privileged
最后手段

--cap-add / --cap-drop

Linux Capabilities 把 root 权限拆成了几十个小块。容器默认只持有其中一部分,但不是最小集合。

# 允许绑定低端口(1024以下)
docker run --cap-add NET_BIND_SERVICE myapp

# 先全部去掉,再按需加回
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp

最小权限原则在这里体现得最直接。生产镜像建议第一步就加 --cap-drop ALL,然后只加真正需要的 capabilities。

常用 capabilities 一览:

Capability 作用
NET_BIND_SERVICE 绑定 1024 以下端口
SYS_PTRACE 进程调试
CHOWN 修改文件所有者
SYS_ADMIN 大量系统操作(慎用)

--security-opt

# 自定义 seccomp profile
docker run --security-opt seccomp=/path/to/profile.json myapp

# 禁用 AppArmor
docker run --security-opt apparmor=unconfined myapp

Seccomp 和 AppArmor 是内核层面的沙箱,默认规则已经很严格。关掉它们往往是某些商业软件(比如杀毒软件)的容器版要求,能不动就不动。

--privileged

docker run --privileged myapp

特权模式,容器基本等于拿到了宿主机 root 权限。可以挂载设备、修改内核参数——风险极高。

真正用到的场景很少,Docker in Docker 是一个。普通业务服务绝对不要开这个,一旦容器被攻破,宿主机也跟着沦陷。


场景十二:健康检查

--health-cmd

进程在跑不等于应用正常。数据库连接池耗尽、API 卡死但进程没崩,这些情况 docker ps 只会显示进程状态正常。

docker run \
  --health-cmd "curl -f http://localhost/ || exit 1" \
  --health-interval 30s \
  --health-timeout 5s \
  --health-retries 3 \
  --health-start-period 10s \
  nginx
  • --health-start-period:留给应用启动的宽限期,冷启动慢的服务需要调大
  • --health-interval:检查频率
  • --health-retries:连续失败几次才标记为 unhealthy

这些参数也可以写在 Dockerfile 里 HEALTHCHECK 指令中,命令行参数会覆盖 Dockerfile 的设置。


场景十三:命令入口控制

--entrypoint 和 CMD 覆盖

# 覆盖 ENTRYPOINT,换成 shell
docker run --entrypoint /bin/sh myapp

# 覆盖 CMD 传入不同参数
docker run myapp --config /etc/myapp/custom.conf

镜像默认入口不适用当前场景时,这两个参数就能派上用场。比如用同一个镜像,测试环境传测试配置,生产环境传生产配置。


完整示例:生产级启动命令

把上面这些拼在一起:

docker run -d \
  --name myapp \
  --network app-net \
  --hostname myapp-01 \
  -p 8080:8080 \
  -e APP_ENV=production \
  --env-file .env \
  -m 512m \
  --cpus 1 \
  --restart unless-stopped \
  -u 1000:1000 \
  --read-only \
  --tmpfs /tmp:size=50m \
  --mount type=volume,source=app-data,target=/app/data \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --health-cmd "curl -f http://localhost:8080/health || exit 1" \
  --health-interval 30s \
  --health-retries 3 \
  myapp:latest

这一长串放到项目里肯定用 Docker Compose 管理更好,但逐条拆开看,能清楚知道每个参数解决的是什么问题。知道问题在哪里,Compose 文件里的 YAML 才写得出来。


快速参考表

参数 作用 常用值
--name 容器命名 统一格式字符串
-e 环境变量 KEY=VALUE
--env-file 从文件注入环境变量 .env
-m 内存上限 512m, 1g
--memory-swap 内存加 swap 上限 1g
--cpus CPU 绝对上限 1, 1.5
--cpu-shares CPU 相对权重 512, 1024
--network 网络模式 bridge, host, 自定义
--add-host 添加 hosts 映射 name:IP
--dns 指定 DNS 服务器 8.8.8.8
--restart 重启策略 unless-stopped, on-failure
-u 运行用户 UID:GID
--read-only 根文件系统只读 无值
--tmpfs 内存临时文件系统 /tmp:size=100m
--mount 显式挂载 type=bind/volume/tmpfs
--log-opt max-size 日志文件大小 10m
--log-opt max-file 日志文件数量 3
--cap-drop ALL 丢弃所有 capabilities 无值
--cap-add 添加指定 capability NET_BIND_SERVICE
--health-cmd 健康检查命令 shell 命令
--rm 退出后自动删除 无值

小结

docker run 的参数不需要全记住,但需要知道两件事:哪些参数必须配,以及哪些坑埋在哪里

必配清单:命名、网络、资源限制、日志大小、重启策略。这五样到位了,生产环境 80% 的问题能提前避免。

安全加固层面,优先把 --cap-drop ALL-u 加上,--read-only 也值得推一把。--privileged 没特殊情况就不用碰。

下一篇文章是 「Docker + Nginx 反向代理 + HTTPS 全套配置实战」,从零把一个带 HTTPS 的反向代理完整跑起来,踩坑实录,敬请期待。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐