Docker 容器化实战踩坑:我被这些问题坑了3次后总结的保命指南
说实话,我第一次用 Docker 的时候,完全是被"一键部署"的宣传给骗进来的。什么"构建一次,到处运行",听起来挺美。结果呢?现实给了我三记响亮的耳光——容器起不来、权限被拒绝、数据全丢光。今天就把这些坑踩了个遍,总结成这份保命指南。
坑一:容器起不来,exit code 127 是个什么鬼?
问题现场
我兴冲冲地写好了 Dockerfile,构建镜像后运行:
dockerbuild-tmyapp.dockerrun 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 权限拒绝的崩溃现场
问题现场
我在容器里挂载了一个本地目录,准备读写文件:
dockerrun-v/home/data:/app/data myapp结果:
PermissionError: [Errno 13] Permission denied: '/app/data/log.txt'原因分析
Docker 容器的默认运行用户是 root(或者在 Kubernetes 里是特定用户),但宿主机目录可能是普通用户创建的。容器内 root 写不进去?这不太对。
更常见的情况是:SElinux 或 AppArmor 限制了容器的操作。
解决方案
方法一:关闭 SElinux(临时)
# 临时禁用setenforce0# 永久禁用(编辑 /etc/selinux/config)SELINUX=disabled方法二:使用 😒 或 :Z 挂载参数
# :z - 多个容器共享# :Z - 私有(每个容器独立)dockerrun-v/home/data:/app/data:Z myapp方法三:运行时指定用户
# 创建普通用户并授权dockerrun-u1000: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 容器,跑得好好的:
dockerrun-d--namemysql-eMYSQL_ROOT_PASSWORD=123456mysql:8某天服务器重启,我重新启动容器:
dockerstart mysql结果:数据库是空的,所有数据都没了!
原因分析
问题在于:我根本没有挂载数据卷!容器内的数据存储在/var/lib/mysql,但这只是容器内部的文件系统。容器一删,数据就没。
# 错误:没有 -v 挂载dockerrun-d--namemysql-eMYSQL_ROOT_PASSWORD=123456mysql:8# 正确:载宿挂主机目录dockerrun-d--namemysql\-eMYSQL_ROOT_PASSWORD=123456\-v/home/mysql/data:/var/lib/mysql\mysql:8解决方案
方法一:宿主机目录挂载
# 创建数据目录mkdir-p/home/mysql/data# 启动时挂载dockerrun-d--namemysql\-eMYSQL_ROOT_PASSWORD=123456\-eMYSQL_DATABASE=mydb\-v/home/mysql/data:/var/lib/mysql\mysql:8方法二:使用 Docker Volume
# 创建数据卷dockervolume create mysql_data# 启动时挂载dockerrun-d--namemysql\-eMYSQL_ROOT_PASSWORD=123456\-vmysql_data:/var/lib/mysql\mysql:8方法三:docker-compose 一键部署
version:'3.8'services:mysql:image:mysql:8environment:MYSQL_ROOT_PASSWORD:123456MYSQL_DATABASE:mydbvolumes:-mysql_data:/var/lib/mysqlrestart:alwaysvolumes:mysql_data:教训
重要的事情说三遍:生产环境一定要挂载数据卷!数据卷!数据卷!没有持久化,容器就是一次性用品。
坑四:端口冲突?容器网络配置的连环坑
问题现场
我启动了两个 Nginx 容器:
dockerrun-d-p80:80--nameweb1 nginxdockerrun-d-p80:80--nameweb2 nginx第二个容器启动失败:
Error response from daemon: driver failed programming external connectivity on endpoint web2: bind: address already in use原因分析
端口被占用了。同一个宿主机上,两个容器不能同时绑定同一个端口。
解决方案
方法一:使用不同宿主机端口
dockerrun-d-p80:80--nameweb1 nginxdockerrun-d-p8080:80--nameweb2 nginx# 映射到 8080方法二:使用 docker-compose 自动分配
version:'3.8'services:web1:image:nginxports:-"80:80"web2:image:nginxports:-"8080:80"方法三:使用 --network 搭建内部网络
# 创建网络dockernetwork create mynet# 启动容器加入网络dockerrun-d--networkmynet--nameweb1 nginxdockerrun-d--networkmynet--nameweb2 nginx# 容器间通过容器名通信dockerexecweb1curlhttp://web2方法四:Docker 内置网络模式
# host 模式(不推荐,会冲突)dockerrun-d--networkhostnginx# bridge 模式(默认)dockerrun-dnginx# none 模式(无网络)dockerrun-d--networknone nginx教训
端口规划要提前做好,生产环境建议用 docker-compose 统一管理网络。
坑五:镜像太大?构建优化的实战技巧
问题现场
我构建了一个 Node.js 镜像,体积居然 1.2GB:
dockerimages REPOSITORY TAG SIZE myapp latest1.2GB这要是部署到服务器,下载镜像就能把人等疯。
原因分析
- 基础镜像太大(用了个 ubuntu 全家桶)
- 把 node_modules、.git 等无关文件一起 COPY 进去了
- 没有清理构建缓存和临时文件
解决方案
方法一:使用 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 确实是现代开发和运维的神器,但它也不是万能的。该踩的坑一个都跑不了。
我的经验是:
- 权限问题优先排查,容器内外用户要对齐
- 数据持久化是底线,没挂载数据卷就等着哭
- 网络配置要提前规划,别等部署时抓瞎
- 镜像优化要从第一天做起,别等体积爆炸才后悔
希望这份踩坑指南能帮你少走弯路。如果觉得有用,点个赞再走?
推荐阅读:
- Docker 官方文档:https://docs.docker.com/
- Docker Compose 最佳实践
- Kubernetes 入门实战(进阶必学)