Docker容器启动失败的常见原因和排查思路

描述

背景

Docker 容器启动失败是日常运维中最常遇到的问题之一。和物理机/虚拟机不同,容器启动失败的原因更加多样化——可能是镜像问题、配置错误、资源不足、网络不通、权限限制、健康检查失败、依赖服务未就绪等多种原因。容器退出后 docker ps 看不到(除非加 -a 参数),日志也可能不够详细,这让排查变得有一定门槛。

本文系统梳理 Docker 容器启动失败的常见原因、排查思路和具体命令,形成一套可直接照着操作的排查流程。适合初中级运维工程师在实际工作中对照使用。

排查总流程

当一个容器启动失败时,按以下顺序排查:

 

第一步:确认容器状态
    └─ docker ps -a | grep 

第二步:查看容器退出信息
    └─ docker logs 
    └─ docker inspect 

第三步:定位根因
    ├─ 镜像问题(镜像不存在、损坏、tag 错误)
    ├─ 配置问题(端口占用、环境变量缺失、命令错误)
    ├─ 资源问题(内存不足、磁盘空间不足)
    ├─ 权限问题(SELinux/AppArmor、用户权限、挂载权限)
    ├─ 网络问题(DNS、端口映射、overlay 网络)
    ├─ 依赖问题(依赖的服务未启动、数据卷未就绪)
    └─ 健康检查(健康检查失败导致重启循环)

第四步:修复并验证
    └─ docker run / docker start

 

第一步:确认容器状态

基本状态查看

 

# 查看所有容器(包括已退出的)
docker ps -a

# 查看特定容器的简要状态
docker ps -a | grep 

# 格式化输出(更清晰)
docker ps -a --format "table {{.ID}}	{{.Names}}	{{.Status}}	{{.Image}}"

 

容器的 Status 字段会显示类似这样的信息:

Exited (1) 5 minutes ago:以退出码 1 退出了

Created:创建了但还没启动

Up 2 hours:正常运行中

Restarting (1) (starting):正在重启(通常意味着不断崩溃重启)

快速判断退出原因

 

# 一行命令看容器状态、退出码、启动命令
docker inspect  --format='{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}} {{.Config.Cmd}}'

# 查看完整状态信息
docker inspect  | jq '.State'

 

第二步:查看容器日志

docker logs

 

# 查看容器日志(stdout + stderr)
docker logs 

# 实时跟踪日志
docker logs -f 

# 查看最近 100 行
docker logs --tail 100 

# 显示时间戳
docker logs --timestamps 

# 查看特定时间的日志
docker logs --since "2024-01-15T1000" 
docker logs --since 30m 

# 如果日志量很大,查看 ERROR 级别日志
docker logs  2>&1 | grep -i error

 

为什么 docker logs 可能看不到日志

这是很多人遇到的问题:docker logs 什么都看不到。原因是:

容器启动命令没有产生 stdout/stderr:如果 CMD 或 ENTRYPOINT 把输出重定向到文件(如 CMD ["python", "app.py", "> /var/log/app.log"]),日志就不会到 docker log driver

日志驱动不对:有些容器使用 json-file 日志驱动,有些用 syslog、fluentd、awslogs 等

日志被轮转清理了

 

# 检查容器的日志驱动
docker inspect  --format='{{.HostConfig.LogConfig.Type}}'

# 如果是 json-file,可以直接查看容器内的日志文件
cat /var/lib/docker/containers//*-json.log | tail -100

 

第三步:查看容器详细信息

docker inspect

docker inspect 是排查容器问题的终极工具,能看到容器从创建到运行的所有配置细节。

 

# 查看完整 inspect 输出(内容很多)
docker inspect 

# 用 jq 提取特定字段
docker inspect  --format='{{json .State}}' | jq .
docker inspect  --format='{{json .Config}}' | jq .
docker inspect  --format='{{json .HostConfig}}' | jq .

# 常见关键字段提取
docker inspect  --format='
State: {{.State.Status}}
ExitCode: {{.State.ExitCode}}
OOMKilled: {{.State.OOMKilled}}
Error: {{.State.Error}}
StartedAt: {{.State.StartedAt}}
FinishedAt: {{.State.FinishedAt}}
Path: {{.Path}}
Args: {{.Args}}
WorkingDir: {{.Config.WorkingDir}}
Cmd: {{.Config.Cmd}}
Entrypoint: {{.Config.Entrypoint}}
Env: {{range .Config.Env}}{{.}} {{end}}
'

 

常见 State 状态及其含义

 

{
  "Status": "exited",
  "Running": false,
  "ExitCode": 127,          // 退出码:127=命令找不到, 1=一般错误, 137=SIGKILL (OOM), 139=SIGSEGV
  "OOMKilled": false,
  "Dead": false,
  "Error": "",              // 如果有错误,这里会显示
  "StartedAt": "...",
  "FinishedAt": "..."
}

 

退出码含义:

退出码 含义 常见原因
0 正常退出 进程执行完成
1 一般错误 应用自己的错误(配置错误、参数错误)
125 Docker daemon 错误 如 --memory 超限导致被 daemon 杀掉
126 命令无法执行 CMD/ENTRYPOINT 权限问题或路径错误
127 命令找不到 PATH 问题或二进制文件不存在
137 SIGKILL (128+9) 被 SIGKILL 杀掉,通常是 OOMKill
139 SIGSEGV (128+11) 段错误,内存越界访问
143 SIGTERM (128+15) 优雅退出(docker stop 触发)

第四步:分类排查

4.1 镜像问题

现象

 

Error: image nginx:1.24 not found
Layer already exists
docker: Error response from daemon: manifest for xxx not found

 

排查命令

 

# 查看本地镜像
docker images

# 查看镜像详细信息
docker inspect nginx:1.24

# 拉取镜像
docker pull nginx:1.24

# 检查镜像 tag 是否存在
docker manifest inspect nginx:1.24

# 清理悬空镜像
docker image prune -f

 

常见场景

tag 写成 latest 但镜像没更新:构建了新镜像但没 push 或没打 tag

registry 地址错误:私有仓库地址写错了

镜像被删除:其他机器 push 覆盖了 tag,但本地还在用旧的 digest

跨架构问题:在 ARM 机器上拉取 x86 镜像(manifest 不兼容)

 

# 检查镜像架构
docker inspect  | grep Architecture

# 查看镜像的完整 digest
docker images --digests

 

修复方案

 

# 重新拉取最新镜像
docker pull :

# 如果是私有仓库
docker login registry.example.com
docker pull registry.example.com/my-app:v1.2.3

# 如果镜像 digest 变了导致启动失败,回滚到旧 digest
docker images --digests | grep 
docker run --rm @sha256:xxxxdigestxxxx

 

4.2 配置问题

4.2.1 端口占用

现象

 

docker: Error response from daemon: Ports are not available:
bind address port already in use.

 

排查命令

 

# 检查哪个进程占用了端口
# Linux
ss -tlnp | grep :80
netstat -tlnp | grep :80

# macOS(没有 ss/netstat)
lsof -i :80

# 查看 dockerd 当前监听的端口
ps aux | grep dockerd

 

修复方案

 

# 方案一:换一个宿主机端口
docker run -p 8080:80 nginx

# 方案二:停止占用端口的服务
systemctl stop nginx
kill $(lsof -t i:80)

# 方案三:检查是否有其他容器占用了同一个端口
docker ps --format "{{.Names}} {{.Ports}}"

 

4.2.2 环境变量缺失或错误

现象

 

# 应用日志
FATAL: Required environment variable DATABASE_URL is not set
# 或者应用直接退出,docker logs 看不到输出

# 容器退出码可能是 0(因为是 shell 脚本检测到缺失后 exit)

 

排查命令

 

# 查看容器的环境变量配置
docker inspect  --format='{{range .Config.Env}}{{.}} {{end}}'

# 对比预期和实际
docker inspect  | jq '.Config.Env'

# 检查容器是否使用了 .env 文件
docker inspect  | grep -i env

# 如果容器还没启动,用 --env-file 检查
docker run --rm --env-file .env my-app:latest env | grep DATABASE

 

修复方案

 

# 运行命令传入环境变量
docker run -e "DATABASE_URL=postgres://user:pass@host:5432/db" my-app

# docker-compose 方式
docker-compose run -e "DATABASE_URL=..." app

# 检查 .env 文件是否存在、格式是否正确
cat .env

 

4.2.3 启动命令错误

现象

 

# 退出码 127 或 126
docker: Error response from daemon: OCI runtime create failed: container_linux.go

# 或应用启动后立即退出,日志显示 "command not found"

 

排查命令

 

# 查看容器配置的 CMD
docker inspect  --format='{{.Config.Cmd}}'

# 查看容器配置的 ENTRYPOINT
docker inspect  --format='{{.Config.Entrypoint}}'

# 手动测试命令是否正确
docker run --rm   

# 进入镜像内测试
docker run --rm -it  sh
# 然后手动执行 CMD 中的命令,看是否报错

 

常见错误

 

# 错误:ENTRYPOINT 和 CMD 顺序反了
# 正确写法:
ENTRYPOINT ["python", "app.py"]
CMD ["--help"]

# 不要写 shell 形式(信号处理问题)
# CMD python app.py  # 不好

# 路径错误
# CMD ["app.py"]  # 如果 WORKDIR 不是 /app 会找不到
CMD ["/app/app.py"]

 

4.3 资源问题

4.3.1 内存不足(OOM)

现象

 

# docker logs 可能看不到任何输出
# docker inspect 显示 OOMKilled: true
# 退出码 137 或 143

 

排查命令

 

# 检查容器是否被 OOMKilled
docker inspect  | grep OOMKilled

# 检查宿主机内存
free -h

# 查看 cgroup 内存限制
cat /sys/fs/cgroup/memory/docker//memory.limit_in_bytes
cat /sys/fs/cgroup/memory/docker//memory.usage_in_bytes

# 查看 dmesg 中的 OOM 记录
dmesg | grep -i oom | tail -20
journalctl | grep -i oom | tail -20

 

修复方案

 

# 增加内存限制
docker run --memory=1g my-app

# 或者去掉限制(不推荐生产环境)
docker run --memory="" my-app

# 检查应用本身的内存问题(内存泄漏)
docker stats --no-stream

 

4.3.2 磁盘空间不足

现象

 

no space left on device
Error: disk quota exceeded
docker: writing tmp: no space left on device

 

排查命令

 

# 检查磁盘使用
df -h

# 检查 Docker 数据目录所在分区
df -h /var/lib/docker

# 查看 Docker 资源占用
docker system df

# 查看具体容器占用
docker ps -s | sort -k3 -rh | head

# 清理 Docker 资源
docker system prune -af
docker builder prune -af

 

修复方案

 

# 清理未使用的资源
docker system prune -a --volumes

# 清理特定容器日志
> /var/lib/docker/containers//*-json.log

# 配置日志轮转(/etc/docker/daemon.json)
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

 

4.4 权限问题

4.4.1 SELinux / AppArmor 阻止操作

现象

 

permission denied: /var/www/html
docker: Error response from daemon: OCI runtime create failed: ... operation not permitted

 

排查命令

 

# 检查 SELinux 状态(CentOS/RHEL)
getenforce
# Enforcing 表示 SELinux 启用

# 查看 SELinux 拒绝日志
ausearch -m AVC -ts recent

# 检查 AppArmor 状态(Ubuntu)
aa-status
apparmor_parser -r /etc/apparmor.d/*

# 查看容器是否以特权模式运行
docker inspect  | grep Privileged

 

修复方案

 

# 方案一:临时关闭 SELinux(不推荐生产环境)
setenforce 0

# 方案二:使用 --privileged(授予所有能力,不推荐)
docker run --privileged my-app

# 方案三:正确配置 SELinux 标签(推荐)
docker run -v /data:/data:Z my-app
# :Z 标签会为容器自动设置正确的 SELinux 上下文

# 方案四:禁用 AppArmor 某个配置文件
# 临时测试用,不推荐生产环境
docker run --security-opt apparmor=unconfined my-app

 

4.4.2 文件系统权限

现象

 

cannot create directory '/var/log/xxx': Permission denied
read-only file system

 

排查命令

 

# 检查容器的只读文件系统设置
docker inspect  | grep ReadonlyRootfs

# 检查挂载卷的权限
ls -la /var/lib/docker/volumes//_data

# 检查容器运行用户
docker inspect  | grep -E "User|WorkingDir"

 

修复方案

 

# 在 Dockerfile 里设置正确的工作目录和用户
WORKDIR /app
RUN chown -R appuser:appuser /app

# 运行容器时指定用户
docker run -u appuser my-app

# 如果需要以 root 运行但访问挂载目录
docker run -u root --privileged my-app

 

4.5 网络问题

4.5.1 依赖服务未就绪(启动顺序)

现象

 

# 容器启动了,但连接数据库失败
# 应用日志:dial tcp 192.168.1.21 connection refused
# 容器可能处于 Restarting 状态

# 这是最常见的"容器启动失败但 logs 看起来正常"的场景

 

排查命令

 

# 检查依赖服务的端口是否可达
docker exec  nc -zv db-host 5432
docker exec  curl -v http://api-host:8080/health

# 检查 DNS 解析
docker exec  nslookup db-host
docker exec  cat /etc/resolv.conf

# 检查容器网络
docker inspect  | grep -A 10 "Networks"

 

修复方案

方案一:使用 depends_on(仅解决启动顺序,不解决等待)

 

# docker-compose.yml
services:
  app:
    image: my-app
    depends_on:
      - db
      - redis
  db:
    image: postgres:15
  redis:
    image: redis:alpine

 

方案二:使用 healthcheck + depends_on condition

 

services:
  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

  app:
    image: my-app
    depends_on:
      db:
        condition: service_healthy

 

方案三:应用层实现重试机制

 

# Python 示例:带重试的数据库连接
import time
import psycopg2

def connect_with_retry(max_retries=10, delay=5):
    for i in range(max_retries):
        try:
            conn = psycopg2.connect(os.environ['DATABASE_URL'])
            return conn
        except psycopg2.OperationalError as e:
            print(f"Attempt {i+1} failed: {e}")
            time.sleep(delay)
    raise Exception("Could not connect to database after retries")

 

4.5.2 健康检查失败导致重启循环

现象

 

# docker ps 显示容器不断重启
# docker inspect 显示 "Healthcheck" 状态非 healthy
# 应用日志正常,但健康检查一直失败

 

排查命令

 

# 查看容器的健康检查配置
docker inspect  | grep -A 10 "Health"

# 查看健康检查日志
docker inspect --format='{{json .State.Health}}'  | jq .

# 查看健康检查的日志输出
docker inspect  | grep -A 5 "Log"

 

常见原因和修复

 

# docker-compose.yml 中的健康检查配置
services:
  app:
    image: my-app
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s   # 启动初期的宽限期

 

常见问题:

curl 不在容器内,需要安装或改用 wget

健康检查路径返回的是非 200 状态码(如 401、403),需要调整判断条件

start_period 太短,应用启动慢,还没 Ready 就开始健康检查了

 

# 测试健康检查命令
docker exec  curl -f http://localhost:8080/health || exit 1

 

4.6 数据卷问题

4.6.1 挂载的宿主机目录不存在

现象

 

docker: Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /data/logs.

 

排查命令

 

# 检查宿主机目录是否存在
ls -la /data/logs

# 检查宿主机目录权限
ls -la /data

# 检查 Docker 数据卷
docker volume ls
docker volume inspect 

 

修复方案

 

# 创建目录
mkdir -p /data/logs
chmod 755 /data/logs

# 如果用 docker-compose,用 volumes_from 或者预先创建 named volume
# named volume 会在不存在时自动创建
docker volume create my-data

 

4.6.2 数据卷权限覆盖问题

现象

 

# 容器启动后,目录是空的(宿主机目录内容被覆盖)
# 或者相反:宿主机目录内容没有反映到容器内

 

原理

bind mount 会完全覆盖容器内的目标目录。如果宿主机 /data 有内容,容器内 /data 原来的内容就不可见了。

修复方案

 

# 不要 bind mount 到已有重要数据的目录
# 用空目录或者专门的目录

# 如果需要同时看到宿主机和容器内的内容,用 volumes(named volume)

# docker-compose 示例
services:
  app:
    volumes:
      - app_data:/var/lib/app

volumes:
  app_data:
    driver: local

 

第五步:完整复现场景练习

场景一:MySQL 容器启动失败

 

# 现象:MySQL 容器启动后立即退出,docker logs 显示密码错误

# 排查步骤
docker ps -a | grep mysql
docker logs 

# 常见原因
# 1. 密码包含特殊字符,环境变量解析问题
# 2. 初始化脚本失败
# 3. 数据目录权限问题

# 修复
docker run -e MYSQL_ROOT_PASSWORD='MyPass@123!' mysql:8.0

# 验证
docker exec -it  mysql -uroot -p'MyPass@123!' -e "SELECT VERSION();"

 

场景二:Redis 容器无法绑定端口

 

# 现象:docker logs 显示 "Creating Server TCP listening socket * bind: Cannot assign requested address"

# 原因:宿主机没有 6379 端口,或者端口被占用了

# 排查
ss -tlnp | grep 6379

# 修复
docker run -p 6379:6379 redis:alpine

 

场景三:Nginx 容器无法访问后端

 

# 现象:Nginx 启动成功,但 502 Bad Gateway

# 排查
docker exec  cat /etc/nginx/conf.d/default.conf
docker exec  ping backend-app
docker exec  nc -zv backend-app 8080

# 检查网络
docker network ls
docker network inspect bridge
docker network inspect 

# 修复:让 nginx 和 backend 在同一个 network
docker network create my-net
docker run --network my-net --name backend my-app
docker run --network my-net --name nginx -p 80:80 nginx

 

场景四:Java 应用容器无法启动

 

# 现象:java.lang.NoClassDefFoundError,容器退出码 1

# 排查
docker logs 
docker run --rm -it  java -version

# 原因
# 1. Java 镜像没有正确安装 JRE/JDK
# 2. 类路径配置错误
# 3. 依赖的 JAR 包没有打包进去

# 检查镜像
docker run --rm -it  ls -la /app
docker run --rm -it  env | grep JAVA

# 修复 Dockerfile
FROM eclipse-temurin:17-jre-alpine
COPY app.jar /app/app.jar
WORKDIR /app
CMD ["java", "-jar", "app.jar"]

 

常用排查命令速查表

 

# 容器状态
docker ps -a
docker ps -a | grep 

# 容器日志
docker logs 
docker logs -f 
docker logs --tail 100 
docker logs --timestamps 

# 容器详情
docker inspect 
docker inspect  --format='{{.State.Status}}'
docker inspect  | jq '.State'

# 镜像
docker images
docker pull :
docker rmi 

# 网络
docker network ls
docker network inspect 
docker exec  cat /etc/resolv.conf

# 资源占用
docker stats --no-stream
docker system df

# 清理
docker system prune -af
docker image prune -af
docker builder prune -af
docker container prune

 

总结

容器启动失败的排查思路总结:

阶段 关键命令 重点关注
确认状态 docker ps -a  + docker logs 退出码、OOMKilled 标记
查看详情 docker inspect State、Config.Env、HostConfig
镜像问题 docker pull  + docker images tag、digest、registry
配置问题 docker inspect Env 环境变量、端口、CMD
资源问题 docker stats  + df -h 内存、磁盘、CPU
权限问题 docker exec  测试命令 SELinux、文件权限、用户
网络问题 docker exec nslookup/curl/nc DNS、端口连通性
数据卷问题 docker volume inspect 挂载路径、权限
依赖问题 docker exec nc -zv 依赖服务端口、healthcheck

最后一条最重要的经验:永远先看 docker logs 和 docker inspect 的输出。这两个命令能解决 80% 的容器启动问题。如果日志里看不出问题,再用 docker run --rm -it sh 手动进入镜像,逐步复现启动过程。

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分