Docker 上手十分钟,上生产却常常踩一串坑:容器重启后数据没了、日志把磁盘写满、容器里的时间比宿主机慢八小时、一个容器跑满 CPU 把整台机器拖死、升级个镜像服务断了两分钟。这些都不是 Docker 的 bug,而是默认配置面向开发环境,生产环境需要另设一遍。
下面 16 项按「镜像 → 运行 → 网络 → 存储 → 日志 → 资源 → 监控 → 升级」排,每条说清楚为什么要设、怎么设、不设会出什么问题。命令以 Docker 20.10+ 与 Compose v2 为准。
镜像层:先解决「不可复现」
- 1. 基础镜像锁版本。
FROM php:8.2-fpm会随上游更新漂移,某天重建镜像突然跑不起来。用带小版本的 tag,或者用 digest:FROM php:8.2.13-fpm@sha256:...。 - 2. 别用 latest。生产用 latest 等于把上线时间交给上游。内部自建镜像用「日期+commit」命名,能追溯。
- 3. 多阶段构建。编译依赖留在 builder 阶段,最终镜像只拷产物。Node 项目从 1.2G 降到 150M 是常见的。
- 4. 非 root 运行。
USER app,配合chown改目录权限。容器里跑 root 的用户逃逸风险高得多,而且挂载卷写出的文件在宿主机上属于 root,后期很麻烦。 - 5. 时区与 locale 写进镜像。
ENV TZ=Asia/Shanghai+ 安装tzdata。这是「日志时间差 8 小时」的唯一根治办法,别在应用里硬编码偏移。
运行层:让容器像个正经服务
- 6. 重启策略。生产统一
restart: unless-stopped。用always的话,你手动停掉的容器重启宿主后会自己起来,容易和运维意图冲突。 - 7. 只读根文件系统。
read_only: true配合tmpfs: /tmp,能挡住大部分「容器内被写入 webshell」的攻击。需要写目录就用卷挂出来。 - 8. 健康检查。
healthcheck里给一个轻量的接口探测,超时 3 秒、重试 3 次。没有健康检查,编排层只知道进程在,不知道服务活没活。 - 9. 优雅停止。应用要处理 SIGTERM,Compose 里设
stop_grace_period: 30s。默认 10 秒,队列任务或大请求会被硬杀。 - 10. 不要在容器里跑 cron 兼职。定时任务单独一个容器或用宿主 crontab 调
docker exec。混在一起会让「重启容器」变成「重启定时任务」。
网络层:端口、DNS 与内外隔离
- 11. 只暴露必要端口,且绑到 127.0.0.1。数据库、Redis 这类只给本机应用用的服务,映射写成
127.0.0.1:6379:6379。0.0.0.0:6379是公网上被挖矿脚本扫到的最常见入口。 - 12. 自定义 bridge 网络。用
networks自建网络,容器间用服务名互访,不要靠--link,也不要用默认 bridge。默认 bridge 下 DNS 解析不工作,是新手常见的「连不上数据库」原因。 - 13. MTU 对齐。跨境或走隧道时,MTU 不匹配会导致大包丢失、页面卡住但 ping 正常。宿主机
ip link看实际 MTU,Docker 用mtu覆盖,通常设 1400 或 1450 更稳。
存储层:数据不能跟着容器走
- 14. 数据全部挂卷,且区分类型。命名卷(named volume)放数据库数据,绑定挂载(bind mount)放配置与代码。容器删了数据还在,这是底线。
- 15. 备份脚本独立于容器。用宿主 cron 定时
docker exec跑 dump,再传到异地。备份和容器放在同一块盘上等于没有备份。每月至少演练一次恢复。
还有一个细节:数据库容器的数据目录权限一旦被改错,重启会报 chown: Operation not permitted。改权限前先停容器,别在运行时动。
日志:不设上限就会写满磁盘
这是 Docker 生产环境最高频的故障,没有之一。默认 json-file 驱动不做轮转,一个话多的应用一周能写出几十 G。
- 16. 全局配日志轮转。
/etc/docker/daemon.json里加:{"log-driver":"json-file","log-opts":{"max-size":"50m","max-file":"3"}},然后systemctl reload docker。单个容器也可以用logging覆盖。 - 对流量大的服务,直接把 driver 换成
local,压缩存储、性能更好。 - 应用日志尽量写到 stdout,交给 Docker 收;不要既写文件又写 stdout,会双份占用。
- 用
docker system df每周看一眼镜像和卷占用,docker image prune定期清无标签镜像。
资源限制:一个容器不能拖死整台机器
默认情况下,任何容器都能吃满宿主机的 CPU 和内存。生产必须设上限:
| 参数 | 作用 | 建议 |
|---|---|---|
--memory | 内存硬上限,超了 OOM Kill | 按应用实测峰值 ×1.5 设 |
--memory-swap | 内存+swap 总上限 | 与 memory 相同,等于禁用 swap |
--cpus | CPU 配额(折算成核数) | 0.5–2 之间按业务设 |
--pids-limit | 进程/线程数上限 | 200–500,防 fork 炸弹 |
--ulimit nofile | 句柄数 | 65535 |
设了 --memory 之后要观察 OOM 计数:docker inspect --format '{{.State.OOMKilled}}' 容器名。如果频繁为 true,说明上限太低或应用有内存泄漏,别直接调大掩盖问题。
监控:三个指标就够定位
- 容器状态与重启次数:重启次数突增说明容器在反复崩溃,
docker events能看到原因。 - 资源使用率:
docker stats的 MEM% 与 CPU%,或接 cAdvisor + Prometheus。关注「接近上限」而不是「当前多少」。 - 健康检查结果:
docker ps的 STATUS 列会显示 unhealthy,这是最早的业务异常信号。
日志侧建议统一收集(Loki + Promtail 或 ELK),至少要能按容器名 grep 到最近一小时的错误,否则出事只能靠猜。
升级:怎么做到不断服
单机场景下,最实用的做法是「双容器 + 反代切换」:
- 起
app_v1与app_v2,Nginx upstream 指向其中一个。 - 部署新版时先起 v2,健康检查通过后改 upstream 指向 v2,
nginx -s reload。 - 观察 10 分钟无 5xx 再停 v1。回滚只需把 upstream 改回去,十秒完成。
数据库变更要单独处理:迁移脚本必须向后兼容(先加字段不改旧字段、双写过渡),否则回滚代码时数据库对不上。这一点比容器编排更重要。
一个可直接用的 Compose 骨架
把上面这些落成配置,大致是这样(节选关键项):
services.app.image: registry/app:20261108-a1b2c3(锁版本)restart: unless-stopped、read_only: true、tmpfs: ["/tmp"]logging.options.max-size: 50m / max-file: 3deploy.resources.limits: cpus 1.0 / memory 1g、pids_limit: 300healthcheck: test curl -f http://localhost/health、interval 15s、retries 3stop_grace_period: 30s、networks: [backend]- 数据库服务只写
127.0.0.1:3306:3306,卷用命名卷,不暴露公网
常见问题
Q:改完 daemon.json 日志轮转,旧日志还在?
轮转只对新建的容器生效。已有容器要重建(docker compose up -d --force-recreate)才会应用新配置。旧的 json 日志文件需要手动清理,注意先停容器再删,否则空间不释放。
Q:容器里时间不对,改了 TZ 还是差 8 小时?
宿主机时间本身可能就有偏差。先 date 看宿主机,再 docker exec 容器 date 对比。宿主机对但容器不对,通常是镜像没装 tzdata;宿主机本身就不对,用 chrony 或 ntpdate 校时。
Q:容器一直在重启,怎么查原因?docker logs --tail 100 容器名 看退出前最后几行;docker inspect --format '{{.State.ExitCode}}' 看退出码(137 是被杀,1 是应用异常)。看不到日志就临时改成 command: sleep 3600 进去手动跑。
Q:单机 Docker 需要上 Kubernetes 吗?
业务规模在两三台机器以内、团队没有专职运维,Compose + 反代足够,上 K8s 的维护成本远高于收益。等出现「多节点调度」「弹性扩缩」「灰度发布」的真实需求再考虑。
Q:容器被入侵了怎么判断?
三个迹象:镜像里出现不认识的进程(docker top)、CPU 长期 100% 但业务量正常(挖矿)、对外出现异常连接(ss -tnp)。发现后先断网再取证,别直接删容器。
小结
16 项里如果只做四件:锁镜像版本、日志加轮转、资源设上限、数据挂卷并异地备份。这四件能挡住生产环境 80% 的事故。剩下的按需补,尤其是「只读文件系统 + 非 root」这两项安全设置,越早做越省事。
再补一个习惯:任何一次变更前,先用 docker compose config 校验配置,再在测试环境跑一遍。Docker 的问题大多是配置问题,而配置问题几乎都能靠「先看一遍生效后的最终配置」提前发现。
相关阅读:服务器性能自测脚本合集、服务器迁移不丢数据的完整清单、中小企业上云第一步、买服务器前必须测的 5 个指标。
本站深度稿系列:从0搭一个扛得住流量的站:LNMP调优清单、Linux磁盘与IO排查:四类故障的定位路径、Windows服务器加固20项:从账号到离线备份。