Docker 容器一直重启退出?6 种根因排查清单,看完不再抓瞎
目录
三、根因二:内存不够被 OOM Kill(Exit 137)
一、容器退出状态码速查
容器一直在 Restarting 状态,第一步看退出码:
docker ps -a
# 输出关键列:
CONTAINER ID STATUS
abc123 Restarting (1) 5 seconds ago ← Exit 1
def456 Restarting (137) 3 seconds ago ← OOM Killed
ghi789 Restarting (255) 10 seconds ago ← 入口点错误
| 退出码 | 含义 | 常见原因 | 频率 |
|---|---|---|---|
| 0 | 正常退出 | 程序执行完就退了(非服务型程序) | 偶尔 |
| 1 | 程序内部错误 | 代码报错、配置文件错误、数据库连不上 | 最常见 |
| 125 | Docker 自身错误 | daemon 出问题 | 罕见 |
| 126 | 命令不可执行 | 权限问题、二进制不兼容 | 偶尔 |
| 127 | 命令找不到 | 路径写错、镜像里没装这个命令 | 常见 |
| 137 | 被 SIGKILL | 内存超限被 OOM Kill | 常见 |
| 139 | 段错误 | 程序 crash(C/C++ 空指针等) | 偶尔 |
| 255 | 未知错误 | 入口点脚本退出码异常 | 常见 |
二、根因一:程序启动就崩(Exit 1)
这是最常见的。程序代码有问题,或者配置不对。排查命令:
# 看容器日志(最重要的一步)
docker logs --tail 100 abc123
# 常见报错:
# 1. 配置文件找不到
FileNotFoundError: [Errno 2] No such file or directory: '/app/config.yaml'
# 2. 数据库连不上
sqlalchemy.exc.OperationalError: connection refused
# 3. 端口被占用
OSError: [Errno 98] Address already in use
# 4. 环境变量缺失
KeyError: 'DATABASE_URL'
# 实时看日志(边重启边看)
docker logs -f abc123
解决方法取决于具体报错,但通用排查思路:
| 报错类型 | 排查命令 | 解决方向 |
|---|---|---|
| 文件找不到 | docker exec abc123 ls -la /app/ | 检查 Dockerfile COPY 路径、volume 挂载 |
| 连接被拒绝 | docker exec abc123 ping db_host | 检查网络、depends_on、启动顺序 |
| 端口占用 | lsof -i :8080 | 换端口或杀掉占用进程 |
| 环境变量缺失 | docker inspect abc123 | grep Env | docker-compose.yml 里加 environment |
三、根因二:内存不够被 OOM Kill(Exit 137)
退出码 137 几乎 100% 是 OOM。Linux 内核发现容器内存超过限制,直接 SIGKILL 杀掉进程。
# 确认是否 OOM
docker inspect abc123 | grep OOMKilled
# 输出:
"OOMKilled": true ← 确认了
# 看内存限制
docker inspect abc123 | grep Memory
# 看 dmesg 里内核日志
dmesg | grep -i "killed process"
# 输出示例:
[1234567.89] Killed process 1234 (python) total-vm:2048000kB, anon-rss:512000kB
解决方案——要么加内存限制,要么优化程序:
# 方法1:加大内存限制(docker-compose.yml)
services:
app:
image: myapp:latest
deploy:
resources:
limits:
memory: 2G # 从 512M 改到 2G
restart: unless-stopped
# 方法2:优化程序内存使用
# Python 示例:用生成器代替列表
# 坏的写法(一次加载全部数据)
data = [json.loads(line) for line in open('big_file.json')]
# 好的写法(逐行处理)
def read_json_lines(path):
with open(path) as f:
for line in f:
yield json.loads(line)
# JVM 应用加内存参数
java -Xms512m -Xmx1536m -jar app.jar
注意:容器内存限制不要设成跟宿主机一样大。Docker 自身、其他容器、操作系统都需要内存。建议容器内存 < 宿主机内存的 70%。
四、根因三:Entrypoint/CMD 配置错误
这是新手最容易犯的错。Dockerfile 里 Entrypoint 或 CMD 写法不对:
# 常见错误1:shell 格式导致信号传递失败
CMD "python app.py" # ❌ 会被解析成 shell -c "python app.py"
CMD ["python", "app.py"] # ✅ exec 格式,直接执行
# 常见错误2:entrypoint 脚本没有执行权限
COPY entrypoint.sh /app/
RUN chmod +x /app/entrypoint.sh # ❌ 忘了这行!
ENTRYPOINT ["/app/entrypoint.sh"]
# 常见错误3:entrypoint 脚本最后一行少了 exec
#!/bin/bash
echo "Starting..."
python migrate.py
python app.py # ❌ 这样 app.py 的 PID 不是 1,收不到 SIGTERM
exec python app.py # ✅ exec 替换当前进程,app.py 成为 PID 1
排查命令:
# 查看镜像的 Entrypoint 和 CMD
docker inspect myapp:latest | jq '.[0].Config.Entrypoint, .[0].Config.Cmd'
# 临时覆盖 entrypoint 进容器排查
docker run -it --entrypoint /bin/sh myapp:latest
五、根因四:健康检查失败导致重启
程序在跑,但健康检查一直失败,Docker 认为容器不健康。如果配了 restart: on-failure,就会被重启。
# 看健康检查状态
docker inspect abc123 | jq '.[0].State.Health'
# 输出示例:
{
"Status": "unhealthy",
"FailingStreak": 5,
"Log": [
{
"ExitCode": 1,
"Output": "curl: (7) Failed to connect to localhost:8080"
}
]
}
排查方向:
| 健康检查方式 | 常见失败原因 | 解决方法 |
|---|---|---|
| curl HTTP 接口 | 端口不对、路径不对、服务还没启动完 | 加 start_period 宽限时间 |
| tcp 端口检查 | 端口没监听 | 检查程序绑定的是 0.0.0.0 还是 127.0.0.1 |
| 自定义脚本 | 脚本权限、依赖缺失 | 容器内手动跑脚本看报错 |
# docker-compose.yml 健康检查配置
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s # ← 启动后 40 秒内失败不算,给程序启动时间
六、根因五:依赖服务没启动
容器 A 依赖数据库,但数据库还没起来,A 启动就报连接失败然后退出,被 restart: always 拉起来,又失败……无限循环。
# docker-compose.yml 的 depends_on 不会等依赖"就绪",只等"启动"
services:
app:
depends_on:
- db # ← 这只保证 db 先启动,不保证 db 已经 ready
restart: always
# 解决:用 healthcheck + depends_on condition
services:
app:
depends_on:
db:
condition: service_healthy # ← 等 db 健康检查通过才启动
restart: always
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
如果不用 docker-compose,程序里可以加重试逻辑:
# Python 示例:等待数据库就绪
import time
import psycopg2
def wait_for_db(max_retries=30, interval=2):
for i in range(max_retries):
try:
conn = psycopg2.connect(
host=os.getenv('DB_HOST', 'localhost'),
port=5432,
dbname=os.getenv('DB_NAME'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASS')
)
conn.close()
print("Database is ready!")
return
except psycopg2.OperationalError:
print(f"Waiting for database... ({i+1}/{max_retries})")
time.sleep(interval)
raise Exception("Database not available after retries")
wait_for_db() # 启动前先等
七、根因六:文件权限问题
容器里用非 root 用户跑,但挂载的 volume 文件权限不对,程序读写不了直接退出。
# 看报错日志
docker logs abc123
# PermissionError: [Errno 13] Permission denied: '/data/cache.db'
# 检查容器内文件权限
docker exec abc123 ls -la /data/
# -rw-r--r-- 1 root root 4096 cache.db ← 属于 root,但容器跑的用户是 appuser
# 解决方法1:Dockerfile 里改权限
COPY --chown=appuser:appuser ./data /data
RUN chown -R appuser:appuser /data
# 解决方法2:启动时改
docker run -v $(pwd)/data:/data --user $(id -u):$(id -g) myapp
# 解决方法3:宿主机改权限
sudo chown -R 1000:1000 ./data # 1000 是容器内 appuser 的 UID
八、排查流程图
Docker 容器重启排查流程容器一直 Restarting看退出码 docker psExit 1: 看日志Exit 137: OOMExit 127: 命令不存在unhealthy: 健康检查docker logs看具体报错加内存限制或优化程序检查 DockerfileCMD/ENTRYPOINT加 start_period检查端口/路径修复 → 验证 → restart: unless-stopped
九、预防方案
| 预防措施 | 怎么做 | 避免什么问题 |
|---|---|---|
| 用 exec 格式 CMD | CMD ["python", "app.py"] | 信号传递失败、进程不退出 |
| entrypoint 脚本加 exec | 最后一行 exec "$@" | PID 1 问题、优雅停机失败 |
| 设合理内存限制 | compose 里 memory: 2G | OOM Kill |
| 加健康检查 + start_period | start_period: 40s | 启动慢被误判为不健康 |
| 依赖用 condition | condition: service_healthy | 依赖服务没就绪 |
| 用 unless-stopped | restart: unless-stopped | 避免无限重启(区别于 always) |
| 非 root 运行 + 正确权限 | Dockerfile 里 USER appuser | 权限拒绝报错 |
十、排查速查表
| 步骤 | 命令 | 看什么 |
|---|---|---|
| 1. 看状态 | docker ps -a | 退出码(Exit Code) |
| 2. 看日志 | docker logs --tail 100 容器名 | 具体报错信息 |
| 3. 看是否 OOM | docker inspect 容器名 | grep OOMKilled | OOMKilled: true |
| 4. 看健康检查 | docker inspect 容器名 | jq .[0].State.Health | FailingStreak + Log |
| 5. 看入口配置 | docker inspect 镜像名 | jq .[0].Config | Entrypoint + Cmd |
| 6. 进容器 | docker run -it --entrypoint /bin/sh 镜像名 | 手动跑命令看报错 |
一句话总结:容器重启问题 90% 看两样东西——退出码 + 日志。退出码告诉你方向,日志告诉你细节。
docker logs是你最好的朋友。
更多推荐


所有评论(0)