背景
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 execnc -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 execcurl -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 execcat /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 logsdocker 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
全部0条评论
快来发表一下你的评论吧 !