Docker 三个月,这 5 个坑我替你踩过了

adminadmin 欧易资讯 2026-07-22 30 0

去年我们组接了一个老项目,前任工程师留下一份 Dockerfile 和一句话:"能跑,别动"。

我没忍住,动了。

然后接下来三个月,我和 Docker 的关系从"工具人"变成了"冤家"。下面这 5 个坑,有的让我重做了镜像,有的让我加班到凌晨,有的让我们的 CI 跑了 40 分钟才发现是个空气问题。

Docker 三个月,这 5 个坑我替你踩过了

环境:Docker 24.0,docker-compose v2.21,主要在 Linux(Ubuntu 22.04)和 Mac M2 上跑。

坑 1:Dockerfile 里 COPY 顺序写错,缓存全废

最早写 Dockerfile,我图省事,这么写:

FROM node:20-alpine

WORKDIR /app

COPY . .

RUN npm install

RUN npm run build

CMD

"node", "dist/index.js"

看着挺好对吧?能跑。

问题是,每次我改了一行业务代码,COPY . . 这一层的缓存就失效了。下面的 npm install 重新跑——3 分钟。npm run build 重新跑——2 分钟。每次推送都要 5 分钟以上构建。

写 Dockerfile 的核心规则我后来才搞明白:变得越频繁的东西,放越后面。

FROM node:20-alpine

WORKDIR /app

# package.json 变得不频繁,先 copy

COPY package*.json ./

RUN npm install # 只要 package.json 没变,这一步走缓存

# 业务代码经常变,放后面

COPY . .

RUN npm run build

CMD

"node", "dist/index.js"

改完之后,日常修代码的构建时间从 5 分钟变成 30 秒。npm install 那一层只要 lockfile 没变就走缓存。

这是 Dockerfile 里最值钱的一条规矩。没有之一。

坑 2:镜像大小爆炸,根因是 npm install 留下的垃圾

第一版镜像我打出来 1.2GB,我以为是 Node 自带的。

后来加上 docker history,发现 npm install 那层就贡献了 800MB。npm 在 install 之后会留下大量缓存和构建依赖,生产环境根本用不上。

# 多阶段构建,builder 装齐所有依赖去构建

FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

# 运行阶段,只装生产依赖

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci --omit=dev && npm cache clean --force

COPY --from=builder /app/dist ./dist

CMD

"node", "dist/index.js"

改完镜像从 1.2GB 降到 280MB。

几个细节:

如果是 Python 项目,对应的招数是 pip install --no-cache-dir,然后多阶段构建。

坑 3:容器里时区是 UTC,日志全错

某天凌晨我们的告警邮件标题写着 2026-04-08 19:23:11 ERROR ...,我一脸懵——那会儿明明是凌晨 3 点多。

原因是镜像基于 alpine,容器里默认 UTC 时区。我们打的日志带时间戳,但时间是 UTC,跟我们北京时间差 8 小时。告警邮件读起来一团乱。

# alpine 上要先装 tzdata

RUN apk add --no-cache tzdata && \

ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \

echo "Asia/Shanghai" > /etc/timezone

或者更简洁:

ENV TZ=Asia/Shanghai

RUN apk add --no-cache tzdata

如果你用 debian-based(node:20、python:3.11)这种,默认就装了 tzdata,直接 ENV TZ=Asia/Shanghai 就行。

这个坑特别隐蔽,因为本地用 docker 跑没问题(你 mount 了 host 的时区),只有上 K8s 或者纯净环境才会暴露。

坑 4:容器跑着跑着没了,根因是 OOM Killer

上线之后跑了 3 天,生产容器频繁重启。日志里啥错误都没有,容器就是突然死了。

排查思路:docker inspect 看 State,OOMKilled: true。

容器内存超出了限制,被宿主机的 OOM Killer 直接干掉。

Node.js 容易出这个问题,因为它默认堆大小是根据宿主机内存算的,不是容器内存限制。

# 你给容器分了 512MB,但 Node 以为自己有 8GB 内存可用

# 然后 V8 把堆撑到 4GB,直接被 OOM

解法:显式告诉 Node 它有多少内存。

ENV NODE_OPTIONS="--max-old-space-size=400"

注意要比容器内存限制留个 buffer。容器 512MB 的话,Node 堆给 400MB,留 100 多 MB 给 buffer、stack、native 库。

Java 应用类似,要传 -XX:MaxRAMPercentage=75 或者 -Xmx400m。

K8s 上,记得 resources 里写清 requests 和 limits,别只写 limits,不然调度可能让节点过载。

坑 5:`docker-compose down` 之后,数据没了

这是我自己踩的最蠢的一个。

本地开发环境用 docker-compose 起了一个 postgres,跑了一周,塞了不少测试数据进去。某天我嫌容器卡,顺手 docker-compose down -v。

-v 是删 volumes。一周数据,瞬间归零。

# docker-compose.yml

services:

db:

image: postgres:16

volumes:

pgdata:/var/lib/postgresql/data # 命名卷

environment:

POSTGRES_PASSWORD: dev

volumes:

pgdata:

docker-compose down(不带 -v)会停掉并删除容器,但保留 volumes。

docker-compose down -v 会连命名卷一起删。

我现在的习惯是:

另外,生产环境用 bind mount 比命名卷更稳——数据落到宿主机文件系统的具体路径,你能直接看到、能 rsync 备份、能在 Docker 之外管理:

volumes:

- /data/pg:/var/lib/postgresql/data

最后一个心得

写了三个月 Docker 后,我发现最大的认知转变是:

Docker 不是"另一种虚拟机"。它是一种把进程、文件、网络打包的方式。

很多人(包括早期的我)把 Docker 当 VM 用,容器里装一堆服务、SSH 进去改东西、用 docker exec 当日常运维入口。这些用法都偏离了它的设计意图,坑都会接踵而至。

容器应该是一次性的、无状态的、单进程的。改东西就重新打镜像、重新部署。这套思路转过来之后,大部分坑会自动消失。

写完这篇,我把它转给我们组那个还在 SSH 进容器改配置的同事看。结果他回我:"那不是更麻烦?"

我没回他。让他自己去踩。

版权声明

本文仅代表作者观点,不代表xx立场。
本文系作者授权xx发表,未经许可,不得转载。

喜欢0评论已闭