做 Docker 部署,最怕的不是报错,而是"看起来启动了,实际上根本不能用"。
容器起来了,页面打不开。 端口映射配了,外部访问不到。 数据库挂载了卷,结果重启后权限炸了。 Nginx 反代写好了,最后只看到一个刺眼的 502。 更烦的是,有些问题不是 Docker 本身出错,而是应用、网络、权限、磁盘、反向代理、环境变量混在一起,把排查搞得一团乱。
这篇文章不讲虚的,直接按生产环境里最常见的故障来拆。你可以把它当成一份 Docker 部署故障排查手册:从容器启动失败,到端口冲突、卷权限、Compose 配置、Nginx 反代、磁盘占满、镜像拉取失败、服务依赖顺序错乱,逐个击破。
一、先记住:Docker 部署问题,本质上就 4 类
绝大多数 Docker 部署故障,最终都能归到下面四层:
镜像层 镜像构建失败、镜像拉取失败、架构不匹配、启动命令错误。
容器层 容器秒退、反复重启、环境变量没生效、进程没真正启动。
网络层 端口冲突、端口映射错误、容器间互联失败、DNS 解析异常、反代打不通。
数据层 卷挂载路径错、文件权限不对、配置文件没覆盖成功、磁盘满、日志爆仓。
你只要按这个思路排,不容易乱。
二、Docker 部署出问题时,正确排查顺序是什么
很多人一上来就改 Compose 文件,改半天,越改越乱。 正确顺序应该是:
第一步:看容器状态
docker ps -a
重点看这几个状态:
- Up:容器运行中
- Exited:容器已退出
- Restarting:容器在反复重启
- Created:创建了但没真正跑起来
如果容器都没活着,别急着看 Nginx,也别急着怀疑网络,先解决容器启动问题。
第二步:看日志
docker logs --tail 200 -f 容器名
或者 Compose 项目:
docker compose logs -f
很多问题,日志第一屏就已经写明白了,比如:
- 配置文件不存在
- 端口被占用
- 数据库连接失败
- 环境变量缺失
- 权限不足
- 启动命令报错
- 依赖服务还没准备好
一句大实话:Docker 问题里,至少一半是日志已经告诉你答案,只是你没看。
第三步:确认端口是否真的监听
宿主机检查端口占用:
ss -ltnp
指定端口检查:
ss -ltnp | grep :80
ss -ltnp | grep :8080
容器内检查服务是否真的监听:
docker exec -it 容器名 sh
进入后执行:
ss -ltnp
如果容器进程活着,但应用根本没监听目标端口,那外部当然访问不到。
第四步:检查配置与挂载
docker inspect 容器名
重点看:
- 挂载路径是否正确
- 环境变量是否传入
- 端口映射是否正确
- 启动命令是否符合预期
- 网络是否加入正确
三、常见问题 1:容器一启动就退出
这是最常见的一类。
典型现象
docker ps -a
看到类似:
Exited (1)Exited (127)Exited (137)Restarting (1)
常见原因
1. 启动命令写错了
比如镜像里根本没有这个命令:
command: npm start
但实际项目里应该跑的是:
command: node server.js
或者工作目录不对,导致命令找不到。
2. 应用主进程跑完就退出
Docker 容器不是虚拟机。 主进程结束,容器就结束。
比如你写了一个一次性脚本,执行完自然退出,这不是 Docker 出错,是容器生命周期结束。
3. 环境变量缺失
比如:
DATABASE_URL not setSECRET_KEY missingPORT undefined
应用直接启动失败。
4. 配置文件路径错
你以为挂载到了 /app/config,实际应用读的是 /etc/app/config.yaml。
5. 内存不足被系统杀掉
如果退出码是 137,通常要警惕:
- 内存不够
- 容器被 OOM Kill
- 宿主机资源耗尽
查看系统日志:
dmesg | tail -n 50
解决方法
先看日志:
docker logs --tail 200 容器名
再看启动命令和环境变量:
docker inspect 容器名
如果是 Compose 部署,优先检查:
services:
app:
image: your-app:latest
environment:
- NODE_ENV=production
- PORT=3000
command: node server.js
四、常见问题 2:容器明明启动了,但外部就是访问不到
这类问题最容易让人误判。
先检查端口映射是否正确
Compose 里常见写法:
ports:
- "8080:3000"
意思是:
- 宿主机 8080
- 映射到容器内 3000
很多人实际应用监听的是 127.0.0.1:3000,而不是 0.0.0.0:3000,这会导致映射失效。
应用应该监听:
0.0.0.0:3000
而不是:
127.0.0.1:3000
典型排查流程
1. 宿主机上测端口
curl http://127.0.0.1:8080
如果本机能通,外部不通,重点看防火墙和云服务器安全组。
2. 容器内测服务
docker exec -it 容器名 sh
curl http://127.0.0.1:3000
如果容器内都不通,那就不是 Docker 映射问题,而是应用根本没正常监听。
3. 检查防火墙
Ubuntu 常见:
ufw status
CentOS / Rocky / AlmaLinux:
firewall-cmd --list-ports
云服务器还要检查:
- 安全组
- 入站规则
- 运营商端口限制
五、常见问题 3:端口冲突,部署直接失败
常见报错
Bind for 0.0.0.0:80 failed: port is already allocated
或者:
Error starting userland proxy
原因
宿主机端口已经被别的服务占用了,比如:
- Nginx
- Apache
- 另一个 Docker 容器
- 本地开发服务
排查命令
ss -ltnp | grep :80
ss -ltnp | grep :443
如果你看到 80 端口已经被别的进程占用,要么停掉它,要么换端口。
解决方案
方案一:改宿主机映射端口
ports:
- "8080:80"
方案二:释放原端口
如果是旧容器占用:
docker ps
docker stop 旧容器名
docker rm 旧容器名
如果是系统服务占用,比如 Nginx:
systemctl stop nginx
systemctl disable nginx
六、常见问题 4:环境变量配了,程序却像没看到一样
这类坑在生产部署里特别多。
常见原因
1. .env 文件没被 Compose 正确读取
2. 变量名写错
例如程序要的是:
DATABASE_URL
你写成了:
DB_URL
3. YAML 格式错误
错误写法:
environment:
NODE_ENV: production
PORT: 3000
这种缩进错一点,结果就全乱。
4. 程序读取方式不对
环境变量已经传进去了,但应用代码没取到。
推荐写法
services:
app:
image: myapp:latest
env_file:
- .env
environment:
- NODE_ENV=production
- PORT=3000
部署后直接进容器确认:
docker exec -it 容器名 env
看变量到底有没有进去。
七、常见问题 5:数据卷挂载后,权限炸了
典型现象
- 容器启动报 Permission denied
- 应用写不了日志
- 数据库初始化失败
- 上传目录无法写入
- 容器里文件能看到,但就是不能改
本质原因
宿主机目录和容器运行用户的 UID/GID 不一致。
很多镜像并不是用 root 跑的。 比如镜像里的应用用户 UID 是 1000,你宿主机挂载目录属主却是 root,那它当然写不进去。
排查方法
查看宿主机目录权限:
ls -lah ./data
查看容器内运行用户:
docker exec -it 容器名 id
解决方法
方案一:调整宿主机目录权限
chown -R 1000:1000 ./data
chmod -R 755 ./data
方案二:显式指定容器用户
services:
app:
image: myapp:latest
user: "1000:1000"
方案三:别乱用 777
很多人上来就是:
chmod -R 777 ./data
这招不是不能用,而是太粗暴。 临时救火可以,长期生产环境不推荐。
八、常见问题 6:Docker Compose 启动成功,但服务其实没准备好
很多人误以为 docker compose up -d 执行完,业务就一定正常。
错。很可能只是容器启动了,应用还没就绪。
典型场景
- Web 服务先启动了,但数据库还没准备好
- 应用依赖 Redis,但 Redis 还没监听端口
- 反向代理起来了,但后端接口还没 ready
解决思路:健康检查
推荐在 Compose 里加 healthcheck:
services:
app:
image: myapp:latest
ports:
- "8080:3000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 3s
retries: 5
数据库服务也可以加:
services:
db:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=123456
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-p123456"]
interval: 10s
timeout: 5s
retries: 10
然后用应用自身的重试机制,别把"依赖服务一瞬间没起来"当成永远失败。
九、常见问题 7:Nginx 反向代理后出现 502 / 504
这类问题,表面看是 Nginx 报错,实际上多数是后端没通。
502 的常见原因
- 后端容器没启动
- Nginx 指向了错误的 upstream 地址
- 容器名写错
- 后端监听地址不对
- 容器间网络不通
504 的常见原因
- 后端响应超时
- 程序卡死
- 数据库查询过慢
- 上游服务未及时返回
正确理解一个关键点
如果 Nginx 和后端都在 Docker Compose 同一个网络里,Nginx 应该通过服务名访问后端,而不是写宿主机 IP。
例如:
location / {
proxy_pass http://app:3000;
}
这里的 app,就是 Compose 里的服务名。
Compose 示例
services:
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
app:
image: myapp:latest
expose:
- "3000"
注意:
ports是对外暴露到宿主机expose是容器网络内可见- 同网络容器之间通信用服务名,不靠 localhost
排查方法
进入 Nginx 容器:
docker exec -it nginx sh
然后测试:
curl http://app:3000
如果这里都访问不到,问题就在容器网络或后端服务本身,不在浏览器,不在域名,也不在玄学。
十、常见问题 8:镜像拉取失败、构建失败
拉取失败常见报错
- pull access denied
- manifest not found
- unauthorized
- TLS handshake timeout
- connection refused
常见原因
- 镜像名写错
- tag 不存在
- 私有仓库没登录
- 网络不通
- DNS 有问题
- 仓库限流
排查思路
1. 先确认镜像名和 tag
docker pull nginx:latest
docker pull mysql:8.0
不要想当然写一个不存在的 tag。
2. 私有仓库先登录
docker login
或者登录你的镜像仓库地址。
3. 检查 DNS
cat /etc/resolv.conf
必要时给 Docker daemon 配 DNS。
构建失败常见原因
- COPY 路径不存在
- .dockerignore 把关键文件排除了
- 构建上下文错了
- 基础镜像拉不下来
- 某一步安装依赖失败
构建时别只看最后一行报错,要往上翻,很多真正原因藏在中间。
十一、常见问题 9:容器之间互相访问失败
典型误区
容器 A 去访问容器 B,结果写成:
http://127.0.0.1:8080
这是大坑。
在容器里,127.0.0.1 指的是当前这个容器自己,不是别的容器,也不是宿主机。
正确写法
如果 Compose 服务名叫 api,端口是 8080:
http://api:8080
排查命令
查看网络:
docker network ls
查看容器是否加入某网络:
docker inspect 容器名
如果两个容器不在同一个 bridge 网络里,互访自然失败。
十二、常见问题 10:磁盘满了,Docker 开始全面发疯
这是线上高危问题。
典型现象
- 容器突然异常
- 日志写不进去
- 数据库报错
- 镜像构建失败
- 拉镜像失败
- 系统卡顿
先看磁盘
df -h
再看 Docker 占用:
docker system df
常见占用大户
- 无用镜像
- 停掉但没删的容器
- 旧卷
- 构建缓存
- JSON 日志文件
清理命令
docker system prune -a
清理前请谨慎,这会删除未使用资源。
如果只是清理悬空资源:
docker image prune
docker container prune
docker volume prune
日志爆仓问题
Docker 默认 json-file 日志驱动,如果不限制,日志能把盘撑爆。
推荐加日志轮转:
services:
app:
image: myapp:latest
logging:
options:
max-size: "10m"
max-file: "3"
这一条很重要,别等磁盘炸了才后悔。
十三、常见问题 11:升级后服务挂了,回滚一脸狼狈
Docker 部署不能只有"上线",没有"回滚"。
推荐做法
1. 不要永远只用 latest
坏习惯:
image: myapp:latest
推荐:
image: myapp:1.2.3
2. 保留上一版镜像
新版本出问题,直接切回旧 tag。
3. 配置和数据分离
镜像升级不应该顺手把数据干没了。
4. 上线前先本地验证
docker compose config
先看最终渲染配置有没有问题。
十四、常见问题 12:系统架构不匹配,镜像跑不起来
这几年这个坑越来越常见,尤其是 Apple Silicon 机器。
常见现象
- 镜像拉下来了,但运行异常
- 某些依赖安装报错
- 提示 exec format error
原因
你的机器架构和镜像架构不匹配,比如:
- 宿主机是 arm64
- 镜像只有 amd64
排查
查看宿主机架构:
uname -m
查看镜像支持的平台时,可以去镜像仓库页面确认。
解决办法
必要时显式指定平台:
services:
app:
image: some-image:latest
platform: linux/amd64
但要注意,跨架构运行可能有性能损耗,也可能带来兼容问题。 生产环境最好还是用匹配架构的镜像。
十五、最稳的一套 Docker Compose 生产基础模板
下面给你一份更稳的基础模板,适合多数 Web 应用参考:
version: "3.9"
services:
app:
image: myapp:1.0.0
container_name: myapp
restart: unless-stopped
env_file:
- .env
environment:
- NODE_ENV=production
- PORT=3000
ports:
- "8080:3000"
volumes:
- ./data:/app/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 3s
retries: 5
logging:
options:
max-size: "10m"
max-file: "3"
nginx:
image: nginx:1.27
container_name: mynginx
restart: unless-stopped
depends_on:
- app
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
logging:
options:
max-size: "10m"
max-file: "3"
这份模板有几个关键点:
- 用明确版本号,不赌 latest
- 有 restart 策略
- 有 healthcheck
- 有日志轮转
- 数据目录单独挂载
- 配置文件只读挂载,降低误改风险
十六、Docker 部署故障排查的万能命令清单
真出问题时,优先把下面这组命令打一遍:
docker ps -a
docker compose ps
docker logs --tail 200 -f 容器名
docker compose logs -f
docker inspect 容器名
docker exec -it 容器名 sh
ss -ltnp
df -h
docker system df
docker network ls
如果你是线上环境,我建议你按下面这个顺序执行:
docker ps -a
docker logs --tail 200 容器名
docker inspect 容器名
ss -ltnp
df -h
这五步,足够先抓出大方向。
十七、上线前自检清单
每次 Docker 部署前,过一遍这张清单,能少踩很多坑:
镜像层
- [ ] 镜像名和 tag 是否正确
- [ ] Dockerfile 是否本地构建验证过
- [ ] 架构是否匹配
容器层
- [ ] 启动命令是否正确
- [ ] 环境变量是否完整
- [ ] 健康检查是否配置
- [ ] 是否设置自动重启
网络层
- [ ] 端口映射是否冲突
- [ ] 服务是否监听 0.0.0.0
- [ ] 容器间调用是否使用服务名
- [ ] 防火墙和安全组是否放行
数据层
- [ ] 卷挂载路径是否正确
- [ ] 目录权限是否匹配
- [ ] 配置文件是否真的被挂载进容器
- [ ] 日志是否做轮转
- [ ] 磁盘空间是否充足
十八、结语:Docker 真正难的,不是命令,而是排查逻辑
很多人觉得 Docker 难,是因为每次出问题都像在开盲盒。 其实不是 Docker 神秘,而是排查没顺序。
记住一句最实用的话:
先确认容器活没活,再确认服务通没通,再确认端口和网络对不对,最后再看配置和数据。
别一出问题就改配置,别一看到 502 就甩锅 Nginx,别一访问不到就怀疑云厂商。
Docker 部署里最可怕的不是报错,而是你根本不知道该先查哪里。
只要顺着这条线排:
容器状态 → 日志 → 端口 → 网络 → 挂载 → 权限 → 磁盘
大部分问题,都能很快定位。
FAQ:几个高频问题补充
1. docker compose up -d 成功了,是不是就代表部署成功?
不是。 这只代表容器启动流程执行了,不代表应用已经 ready,也不代表外部一定能访问。
2. 容器里访问 localhost 为什么打不通别的服务?
因为容器里的 localhost 只指向当前容器自己。 容器互访应该用服务名。
3. 容器重启后数据没了,是 Docker 不稳定吗?
通常不是。 大概率是你没挂卷,或者数据写进了容器层,容器删除后自然没了。
4. 为什么我本机能访问,外网访问不到?
优先查:
- 应用是否监听 0.0.0.0
- 宿主机防火墙
- 云服务器安全组
- 端口是否映射正确
5. 为什么 Docker 越跑越占磁盘?
常见原因是:
- 老镜像没清
- 旧容器没删
- 构建缓存堆积
- 日志没做轮转
问题求助
没能解决你的问题?直接问我
如果你遇到任何技术问题无法解决,可以在这里提交求助。我会尽快查看并回复你。
支持作者
如果这篇文章帮到了你,可以支持我
扫码打赏,支持我持续更新原创排障文章。

