Docker 容器化实战踩坑:我被这些问题坑了3次后总结的保命指南

说实话,我第一次用 Docker 的时候,完全是被"一键部署"的宣传给骗进来的。什么"构建一次,到处运行",听起来挺美。结果呢?现实给了我三记响亮的耳光——容器起不来、权限被拒绝、数据全丢光。今天就把这些坑踩了个遍,总结成这份保命指南。

坑一:容器起不来,exit code 127 是个什么鬼?

问题现场

我兴冲冲地写好了 Dockerfile,构建镜像后运行:

docker build -t myapp .
docker run myapp

结果输出:

bash: myapp: command not found
exit code 127

原因分析

exit code 127 表示"命令找不到"。问题出在 Dockerfile 的 CMD 指令:

# 错误写法
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y python3
CMD myapp  # 容器里根本没有这个命令!

容器内的环境和你本地完全不同,你宿主机上的可执行文件路径在容器里根本不存在。

解决方案

方法一:使用完整路径

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y python3
WORKDIR /app
COPY . .
CMD ["/usr/bin/python3", "app.py"]  # 使用完整路径

方法二:在容器内安装你的应用

# 推荐:构建时就把应用装进去
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]

方法三:使用 ENTRYPOINT + ARG

FROM python:3.9
WORKDIR /app
COPY . .
ENTRYPOINT ["python"]
CMD ["app.py"]

教训

容器不是虚拟机,它只认容器内的路径。写 CMD 之前,先想想这个命令在容器里能不能找到。


坑二:Permission Denied?Docker 权限拒绝的崩溃现场

问题现场

我在容器里挂载了一个本地目录,准备读写文件:

docker run -v /home/data:/app/data myapp

结果:

PermissionError: [Errno 13] Permission denied: '/app/data/log.txt'

原因分析

Docker 容器的默认运行用户是 root(或者在 Kubernetes 里是特定用户),但宿主机目录可能是普通用户创建的。容器内 root 写不进去?这不太对。

更常见的情况是:SElinux 或 AppArmor 限制了容器的操作

解决方案

方法一:关闭 SElinux(临时)

# 临时禁用
setenforce 0

# 永久禁用(编辑 /etc/selinux/config)
SELINUX=disabled

方法二:使用 😒 或 :Z 挂载参数

# :z - 多个容器共享
# :Z - 私有(每个容器独立)
docker run -v /home/data:/app/data:Z myapp

方法三:运行时指定用户

# 创建普通用户并授权
docker run -u 1000:1000 -v /home/data:/app/data myapp

方法四:Dockerfile 中创建用户

FROM ubuntu:20.04
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
RUN chown -R appuser:appgroup /app
USER appuser
COPY --chown=appuser:appgroup . .
CMD ["python", "app.py"]

教训

容器权限不是小事,生产环境千万不要用 root。创建专用用户,文件权限要对齐。


坑三:重启容器后数据全没了?数据卷挂载的致命错误

问题现场

我部署了一个 MySQL 容器,跑得好好的:

docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8

某天服务器重启,我重新启动容器:

docker start mysql

结果:数据库是空的,所有数据都没了!

原因分析

问题在于:我根本没有挂载数据卷!容器内的数据存储在 /var/lib/mysql,但这只是容器内部的文件系统。容器一删,数据就没。

# 错误:没有 -v 挂载
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8

# 正确:载宿挂主机目录
docker run -d --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /home/mysql/data:/var/lib/mysql \
  mysql:8

解决方案

方法一:宿主机目录挂载

# 创建数据目录
mkdir -p /home/mysql/data

# 启动时挂载
docker run -d --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -e MYSQL_DATABASE=mydb \
  -v /home/mysql/data:/var/lib/mysql \
  mysql:8

方法二:使用 Docker Volume

# 创建数据卷
docker volume create mysql_data

# 启动时挂载
docker run -d --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql_data:/var/lib/mysql \
  mysql:8

方法三:docker-compose 一键部署

version: '3.8'
services:
  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: mydb
    volumes:
      - mysql_data:/var/lib/mysql
    restart: always

volumes:
  mysql_data:

教训

重要的事情说三遍:生产环境一定要挂载数据卷!数据卷!数据卷! 没有持久化,容器就是一次性用品。


坑四:端口冲突?容器网络配置的连环坑

问题现场

我启动了两个 Nginx 容器:

docker run -d -p 80:80 --name web1 nginx
docker run -d -p 80:80 --name web2 nginx

第二个容器启动失败:

Error response from daemon: driver failed programming external connectivity on endpoint web2: bind: address already in use

原因分析

端口被占用了。同一个宿主机上,两个容器不能同时绑定同一个端口。

解决方案

方法一:使用不同宿主机端口

docker run -d -p 80:80 --name web1 nginx
docker run -d -p 8080:80 --name web2 nginx  # 映射到 8080

方法二:使用 docker-compose 自动分配

version: '3.8'
services:
  web1:
    image: nginx
    ports:
      - "80:80"
  web2:
    image: nginx
    ports:
      - "8080:80"

方法三:使用 --network 搭建内部网络

# 创建网络
docker network create mynet

# 启动容器加入网络
docker run -d --network mynet --name web1 nginx
docker run -d --network mynet --name web2 nginx

# 容器间通过容器名通信
docker exec web1 curl http://web2

方法四:Docker 内置网络模式

# host 模式(不推荐,会冲突)
docker run -d --network host nginx

# bridge 模式(默认)
docker run -d nginx

# none 模式(无网络)
docker run -d --network none nginx

教训

端口规划要提前做好,生产环境建议用 docker-compose 统一管理网络。


坑五:镜像太大?构建优化的实战技巧

问题现场

我构建了一个 Node.js 镜像,体积居然 1.2GB:

docker images
REPOSITORY   TAG      SIZE
myapp        latest   1.2GB

这要是部署到服务器,下载镜像就能把人等疯。

原因分析

  1. 基础镜像太大(用了个 ubuntu 全家桶)
  2. 把 node_modules、.git 等无关文件一起 COPY 进去了
  3. 没有清理构建缓存和临时文件

解决方案

方法一:使用 alpine 精简镜像

# 原始镜像
# FROM node:18          # 900MB+

# 优化后
FROM node:18-alpine     # 170MB

方法二:使用多阶段构建

# 第一阶段:构建
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# 第二阶段:运行(只复制必要文件)
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

方法三:.dockerignore 排除无关文件

# .dockerignore
node_modules
.git
.DS_Store
*.log
.env
.vscode

方法四:按需安装依赖

FROM python:3.9-slim
WORKDIR /app
# 只复制依赖文件
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制源码
COPY . .
CMD ["python", "app.py"]

最终效果

优化前:1.2GB
优化后:约 150MB

教训

镜像体积直接影响部署速度,能用 slim 就不用 full,能用 alpine 就不用 ubuntu。


写在最后

Docker 确实是现代开发和运维的神器,但它也不是万能的。该踩的坑一个都跑不了。

我的经验是:

  1. 权限问题优先排查,容器内外用户要对齐
  2. 数据持久化是底线,没挂载数据卷就等着哭
  3. 网络配置要提前规划,别等部署时抓瞎
  4. 镜像优化要从第一天做起,别等体积爆炸才后悔

希望这份踩坑指南能帮你少走弯路。如果觉得有用,点个赞再走?


推荐阅读:

  • Docker 官方文档:https://docs.docker.com/
  • Docker Compose 最佳实践
  • Kubernetes 入门实战(进阶必学)
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐