跳到主内容
快讯直播
AI智模界
教程

Docker 容器起不来?先查这 5 个地方

容器"起不来"通常有三种表现:docker run 敲下去就返回一个容器 ID,但 docker ps 里找不到它;容器出现了,状态是 Exited (1) 或者反复 Restarting;命令干脆直接报错,容器根本没被创建。下面这套排查顺序,覆盖了绝大多数场景。

报错现象

常见的报错原文有这么几类:

```

docker: Error response from daemon: driver failed programming external connectivity on endpoint web:

Bind for 0.0.0.0:8080 failed: port is already allocated.

```

```

standard_init_linux.go:xxx: exec user process caused "exec format error"

```

```

docker: Error response from daemon: no matching manifest for linux/amd64 in the manifest list entries.

```

```

/app/start.sh: permission denied

```

以及没有明显报错、只是静默退出的:

```

$ docker ps -a

CONTAINER ID IMAGE STATUS PORTS NAMES

a1b2c3d4e5f6 myapp:dev Exited (1) 8 seconds ago myapp

```

影响范围从"单个容器起不来"到"整台机器上所有容器都起不来"都有可能。先确认是个例还是全局:如果同一台机器上新起的容器全部失败,优先怀疑磁盘、Docker 守护进程、内核或资源问题,而不是应用代码。

可能原因

按出现概率从高到低:

1. 应用自身启动就失败。配置缺失、连不上数据库、启动脚本路径写错、CMD 里命令拼错,容器进程一启动就退出。对应退出码 1、126、127、255。

2. 端口被占用或端口映射写错。宿主机端口已被别的进程或别的容器占用,或者 -p 两边写反。

3. 挂载路径有问题。宿主机目录不存在、写成了相对路径被当成 volume 名、把镜像里的目录整个盖掉了、挂载后权限不对。

4. 权限与资源限制。容器内进程是非 root 用户但要去写 root 拥有的目录;被 cgroup 内存限制触发 OOM Killer 杀掉(退出码 137);SELinux / AppArmor 拦截。

5. CPU 架构不匹配。镜像构建在 linux/arm64 上,跑到 linux/amd64 机器上(或反过来),直接 exec format error

逐条排查与解决

第一处:退出码——判断是"自己退出"还是"被杀掉"

```bash

docker ps -a

docker inspect --format '{{.State.ExitCode}} OOM={{.State.OOMKilled}} {{.State.Error}}' 容器名

```

退出码的含义是通用约定:

退出码含义常见场景
0主进程正常结束一次性任务跑完了;常驻服务却返回 0,说明它没有前台进程
1应用层通用错误配置错、依赖连不上,需要看日志
125docker 客户端/守护进程层面的失败docker run 参数写错
126命令存在但没有执行权限脚本没加可执行位
127命令找不到镜像里没装这个命令,或 PATH 不对
128+N被信号 N 终止137 = 128+9(SIGKILL,多为 OOM 或 stop 超时);143 = 128+15(SIGTERM);139 = 段错误
255应用自己返回的错误需要看应用日志

判断方法:看到 137 且 OOMKilled=true,就去查内存限制;看到 126 / 127,基本可以锁定启动命令或执行权限问题,不用再猜业务逻辑。

第二处:日志——看容器自己说了什么

```bash

docker logs --tail 200 容器名

docker logs -f 容器名

docker logs --since 10m 容器名

```

日志为空说明两件事之一:进程还没跑到打印日志就退出了,或者应用把日志写进了容器内的文件而不是 stdout/stderr。后者很常见,表现就是"容器一直重启,日志一条没有"。

想进一步确认,可以覆盖入口命令进容器看:

```bash

docker run --rm -it --entrypoint sh 镜像名

```

进去之后手动执行 Dockerfile 里 CMD / ENTRYPOINT 的那条命令,看它报什么。

第三处:端口与挂载

端口先看有没有被占:

```bash

docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

ss -ltnp | grep :8080

ss -lunp | grep :8080

```

ss 输出里能直接看到占用该端口的进程 PID 和程序名。如果占用方是 docker-proxy,说明是另一个容器在用这个端口,用第一条命令就能找出是哪个容器。想快速验证是不是端口冲突,换一个宿主机端口再起一次即可。

挂载先看实际挂了什么:

```bash

docker inspect --format '{{json .Mounts}}' 容器名

```

几个高频坑:

  • 宿主机目录不存在时,bind mount 会自动创建一个空目录,容器里看到的是空目录,应用以为配置文件丢了,于是报错退出。
  • -v data:/app/data 这种写法里 data 是卷名,不是路径。想用路径必须写绝对路径 /srv/data:/app/data
  • 把宿主机目录挂到 /app 这种代码目录上,会把镜像里原有的文件全部盖掉,容器里就只剩你挂进去的东西。
  • 排查阶段建议改用 --mount,它在源路径不存在时会直接报错,而不是悄悄造一个空目录:

```bash

docker run --rm --mount type=bind,src=/srv/conf,dst=/app/conf,readonly 镜像名

```

Read-only file system 时,检查是不是加了 --read-only 或者挂载带了 :ro,需要写入的目录要用 --tmpfs 或单独的卷放出来。

第四处:权限

```bash

docker inspect --format '{{.Config.User}}' 镜像名

ls -ln /srv/data

```

ls -ln 看的是数字 UID/GID,这一点很关键:容器里跑的是 UID 1000,宿主机目录属主也是 1000,看起来"名字不一样但数字一样",实际是能写的。反过来,如果容器内进程是 UID 1000、宿主机目录属主是 root 且权限是 755,写操作就会 Permission denied

解决方式按场景选:

```bash

sudo chown -R 1000:1000 /srv/data

```

或者在 Dockerfile 里显式指定一个和宿主机匹配的 UID:

```dockerfile

RUN useradd -u 1000 -m appuser

USER 1000

```

CentOS / RHEL / Fedora 这类启用 SELinux 的系统,还要看挂载标签:

```bash

ls -Z /srv/data

```

需要时在挂载参数后加 :z(多个容器共享)或 :Z(独占)。

注意区分一个容易混淆的报错:

```

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock

```

这是当前用户没有权限访问 Docker 套接字,和容器内部权限没有关系,处理方式是加入 docker 组或用 sudo,具体做法以官方文档当前版本为准。

第五处:架构

```bash

uname -m

docker version --format '{{.Server.Arch}}'

docker image inspect 镜像名 --format '{{.Architecture}} {{.Os}}'

```

两边的架构对不上,就会出现 exec format error,或者拉镜像阶段就报 no matching manifest

典型场景:在 Apple 芯片的 Mac 上构建出来的镜像,直接推到 x86 服务器上跑;反过来在 x86 上构建的镜像拿到 arm 服务器上跑。

处理方式:

```bash

docker run --platform linux/amd64 镜像名

```

前提是宿主机已经注册了对应的模拟执行支持,具体配置方式以官方文档当前版本为准。更稳妥的做法是用 buildx 一次性构建多架构镜像:

```bash

docker buildx build --platform linux/amd64,linux/arm64 -t 镜像名:tag --push .

```

需要说明的是,跨架构模拟执行的性能会明显下降,部分依赖特定指令的应用也可能跑不稳,能构建原生架构镜像就不要长期依赖模拟。

都不管用时的兜底方案

1. 绕开所有包装层,最小化复现:不用 compose、不用自定义 entrypoint,直接 docker run --rm -it --entrypoint sh 镜像名,手动执行启动命令。

2. 看守护进程日志journalctl -u docker --since "30 min ago",桌面版 Docker 则看它的日志界面。

3. 看资源与存储docker info 关注存储驱动和告警,df -h /var/lib/docker 确认磁盘没满,磁盘写满会让新容器直接创建失败。

4. 观察事件流:另开一个终端跑 docker events,再启动容器,能看到状态变化的完整过程。

5. 检查编排文件:用 docker compose config 校验语法,去掉 -d 前台启动,报错会直接打到终端。

6. 重启守护进程sudo systemctl restart docker。注意这会停掉机器上所有运行中的容器,生产环境要先确认影响面。

如何预防再次发生

  • 镜像里显式声明非 root 用户和固定 UID,别让容器以 root 跑。
  • 挂载统一用 --mount 写绝对路径,配置文件优先用环境变量或配置卷,少挂整个代码目录。
  • 应用日志打到 stdout/stderr,别只写文件,否则排查时等于没有日志。
  • 端口在编排文件里集中管理,起新服务前先 ss -ltnp 确认端口空闲。
  • 给容器设 --memory--cpus 上限,并配上 restart: unless-stoppedon-failure 策略,让偶发崩溃能自愈,同时避免单个容器吃光整机内存。
  • 构建阶段就用 buildx 产出多架构镜像,部署前在目标架构上跑一次冒烟测试。
  • 给关键服务加 healthcheck,让"起来了但没就绪"这类问题暴露在编排层,而不是等到用户报障。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。