Docker踩坑实录:上线第一个月,我遇到的5个生产环境问题(2026版)

Docker用过都说好,用好都是踩过来的。我的服务器上跑了8个容器,上线第一个月,镜像构建挂了3次、容器启动顺序乱了2次、磁盘被日志撑爆了1次。这篇把我踩过的5个坑整理出来,每个都有完整解决方案。


坑1:镜像构建体积过大

现象

部署的时候,镜像传了20分钟还没传完。一看镜像大小:3.2GB。Node.js应用而已,怎么这么大?

原因

问题出在Dockerfile上。我的原始写法是这样的:

FROM node:18
WORKDIR /app
COPY . .                       # 把整个项目包括node_modules都复制进去
RUN npm install                # 然后再装一遍依赖
EXPOSE 3000
CMD ["node", "server.js"]

问题在哪?COPY . .node_modules 也复制进去了(里面可能有本地编译的二进制模块,体积巨大),然后 npm install 又重新装了一遍。最后镜像里会有两份依赖。

解决

使用多阶段构建,只复制必要文件:

# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 运行阶段
FROM node:18-slim
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

另外,加上 .dockerignore 文件,排除不需要的文件:

node_modules
.git
*.log
dist
coverage

镜像体积从3.2GB降到280MB,构建时间从4分钟降到45秒。


坑2:容器启动顺序问题

现象

docker-compose up 启动时,Java应用容器报了连接错误:

Caused by: java.net.ConnectException: Connection refused
    (in java.net.PlainSocketImpl)

但等10秒再试,又好了。

原因

MySQL容器还没完成初始化(数据库启动、安全加固、创建表),Java应用容器就已经开始连接了。docker-compose up 默认不等待容器"就绪",只等待容器"启动"。

解决

方案1:用wait-for脚本(最通用)

# docker-compose.yml
version: "3.8"
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 5s
      retries: 10

  app:
    image: my-java-app
    depends_on:
      db:
        condition: service_healthy     # 等待db healthcheck通过
    command: ["./wait-for-it.sh", "db:3306", "--", "java", "-jar", "app.jar"]

方案2:用healthcheck+condition(Docker Compose原生)

在db服务加 healthcheck,在app服务的 depends_oncondition: service_healthy

方案3:用depends_on + sleep(不推荐,但简单)

不推荐是因为sleep时间是拍脑袋的,网络慢的时候还是会挂。

depends_on 只保证启动顺序,不保证服务就绪。用 healthcheck + condition: service_healthy 才能真正解决。


坑3:内存限制不生效

现象

Java应用设置了Docker内存限制为512MB,但应用启动时JVM报告使用了8GB内存,最终OOM崩溃。

原因

JVM默认使用物理机的内存,而不是容器限制的内存。-Xmx 如果不设置,JVM会探测物理机总内存然后给自己分配一个比例,通常是1/4。容器限制对JVM不起作用。

解决

正确传递容器内存限制给JVM:

# 方式1:用环境变量(推荐)
FROM openjdk:17-slim
# 不设置-Xmx,让JVM自动检测容器内存
# 但需要JDK 10+,并且加上 -XX:+UseContainerSupport
CMD java -XX:+UseContainerSupport -jar app.jar

# 方式2:显式指定(精确控制)
CMD java -XX:MaxRAM=$(echo $((512*1024*1024))) -jar app.jar

Spring Boot 2.4+ 自带容器内存感知,不需要额外配置。

JVM不自动识别容器内存限制。JDK 10以上加 -XX:+UseContainerSupport,或者手动传 -XX:MaxRAM


坑4:跨平台构建导致的不兼容

现象

Mac M1上构建的Node.js镜像,传到Linux服务器上跑,报错:

Illegal instruction

原因

Mac M1是ARM64架构,Linux服务器是AMD64(x86_64)架构。用Docker默认构建,镜像的平台就是当前机器的平台。M1上构建的镜像是 linux/arm64,但服务器CPU不认识ARM64指令。

解决

方法1:指定平台构建(推荐)

# 构建时指定目标平台
docker buildx build --platform linux/amd64 -t my-app:latest .

# 或者在docker-compose.yml里指定
services:
  app:
    build:
      context: .
      platform: linux/amd64    # 指定目标平台

方法2:使用alpine基础镜像

# 用 alpine 版本,基本兼容所有平台
FROM node:18-alpine

构建机器和运行机器平台不一致,是最隐蔽的坑。始终用 --platform linux/amd64 构建,或者用 alpine 基础镜像。


坑5:日志撑爆磁盘

现象

部署第二天,服务器SSH连不上了。一查,磁盘100%满了。

原因

Docker默认把容器日志写入 /var/lib/docker/containers/<container-id>/<container-id>-json.log,并且没有轮转。日志一直写,越积越多。我的Java应用debug日志开着,一天能写10GB。

解决

方法1:配置日志轮转(生产环境推荐)

修改 /etc/docker/daemon.json(需要sudo):

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  }
}

然后重启Docker:

sudo systemctl restart docker

方法2:在docker-compose里配置(更灵活)

services:
  app:
    image: my-app
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "3"

方法3:定期清理日志(补充手段)

# 清理超过7天的日志文件
find /var/lib/docker/containers -name "*.log" -mtime +7 -delete

日志磁盘占满是生产环境的常见事故。部署第一天就配置好日志轮转,不要等磁盘满了再处理。


完整Dockerfile示例(Node.js生产环境)

# ========== 构建阶段 ==========
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# ========== 运行阶段 ==========
FROM node:18-alpine
WORKDIR /app

# 从builder阶段复制依赖
COPY --from=builder /app/node_modules ./node_modules
COPY . .

# 非root用户运行(安全最佳实践)
RUN addgroup -g 1001 -S nodejs && adduser -S nodeapp -u 1001
USER nodeapp

EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

总结

原因解决方案
镜像体积过大COPY . . + 多余依赖多阶段构建 + .dockerignore
启动顺序depends_on 不等就绪healthcheck + condition
内存限制不生效JVM不识别容器限制-XX:+UseContainerSupport
跨平台不兼容构建平台与运行平台不一致--platform linux/amd64
日志撑爆磁盘无日志轮转daemon.json + max-size

Docker的坑大多数是"看起来能用,但上线就挂"类型的。建议每条都提前预防,不要等踩了再补。

更多推荐