Docker前端容器反复重启重复执行/start脚本问题排查
问题根因
容器无限重启的核心原因非常明确:Docker容器的生命周期与PID 1进程(也就是你的启动命令/start对应的进程)完全绑定,一旦PID 1进程执行结束退出,容器就会终止,你配置了restart: always策略,Docker就会不断重新拉起容器,形成循环重启。
查看你的/start脚本可以发现,整个脚本执行完安装包、建目录、写配置的操作后,只输出了一行certbot文本就直接结束了,从头到尾没有启动nginx服务,脚本执行完成后PID 1进程直接退出,自然触发重启。
另外你的启动脚本还存在几个设计问题,会导致后续异常:
- 把
apk update、安装nano/certbot这类应该在镜像构建阶段执行的操作放到了启动脚本里,每次容器重启都要重复下载安装包,拖慢启动速度 - 创建目录、写nginx配置的操作没有做存在性判断,重启时重复执行直接报
File exists错误 - 写letsencrypt配置用了
>>追加模式,每次重启都会往配置文件里重复写入相同的配置段,后续启动nginx会直接报配置重复的错误 - 你之前添加的
stdin_open: true配置仅用于打开容器标准输入,和进程驻留逻辑完全无关,对解决这个问题没有任何作用。
排查验证步骤
- 查看容器退出码:执行
docker inspect frontend --format='{{.State.ExitCode}}',如果返回码为0,说明是进程正常执行完毕退出,不是运行时崩溃,和当前现象完全匹配 - 临时调试容器:修改docker-compose.yml中frontend服务的command为
command: tail -f /dev/null,执行docker-compose up -d frontend拉起容器后,用docker exec -it frontend sh进入容器,手动执行/start,执行完会直接回到shell提示符,执行ps aux看不到任何nginx相关进程,即可验证问题 - 查看容器运行时长:执行
docker ps会看到frontend容器的启动时间一直在更新,存活时间极短,符合反复重启的特征
修复方案
1. 调整Dockerfile,把构建阶段该做的操作提前到镜像构建时执行
修改前端镜像的Dockerfile,将依赖安装、目录创建、固定配置写入这类不需要每次启动都执行的操作,放到构建阶段完成,避免重复执行:
FROM node:14-alpine as frontend-builder WORKDIR /app/frontend COPY ./frontend . ENV PATH ./node_modules/.bin/:$PATH RUN set -ex; \ yarn install --frozen-lockfile --production; \ yarn cache clean; \ yarn run build ############################################### FROM nginx:1.19.2-alpine # 构建阶段安装依赖,仅在build时执行一次 RUN apk update && apk add --no-cache nano certbot RUN rm /etc/nginx/conf.d/default.conf COPY ./compose/production/build/nginx.conf /etc/nginx/conf.d # 提前创建所需目录、配置好固定权限 RUN mkdir -p /var/lib/letsencrypt/.well-known \ && chgrp www-data /var/lib/letsencrypt \ && chmod g+s /var/lib/letsencrypt \ && mkdir -p /etc/nginx/snippets # 提前写入固定的nginx配置,注意转义$uri避免被shell解析 RUN echo "location ^~/.well-known/acme-challenge/ {" >/etc/nginx/snippets/letsencrypt.conf \ && echo " allow all;">> /etc/nginx/snippets/letsencrypt.conf \ && echo " root /var/lib/letsencrypt/;">> /etc/nginx/snippets/letsencrypt.conf \ && echo " default_type \"text/plain\";" >> /etc/nginx/snippets/letsencrypt.conf \ && echo " try_files \$uri =404;">> /etc/nginx/snippets/letsencrypt.conf \ && echo "}">> /etc/nginx/snippets/letsencrypt.conf COPY ./compose/production/build/start /start RUN chmod +x /start COPY --from=frontend-builder /app/frontend/build /usr/share/nginx/html/build
2. 重写/start启动脚本,最后启动nginx前台进程常驻
修改/start脚本,只保留每次启动需要动态执行的逻辑(比如证书申请、配置校验),最后以前台模式启动nginx,让进程持续阻塞不退出:
#!/bin/sh set -e # 此处可添加需要动态执行的逻辑,比如证书不存在时申请certbot证书、配置校验等 # 注意不要重复写入已经在构建阶段生成的固定配置 # 以前台模式启动nginx,作为PID 1进程常驻,阻止脚本退出 nginx -g 'daemon off;'
3. 重新构建部署
执行以下命令重新构建前端镜像并启动:
docker-compose build frontend docker-compose up -d frontend
额外注意事项
- certbot证书申请逻辑建议加判断:仅当证书不存在或即将过期时才执行申请/续签,避免每次容器启动都调用Let's Encrypt接口触发限流
- 证书续签建议单独用定时任务或者sidecar容器实现,不要把所有逻辑都堆在启动脚本中
- 启动脚本开头建议加
set -e,任意步骤执行失败直接退出,方便排查问题,避免异常情况下容器带病运行
内容的提问来源于stack exchange,提问作者user19271630
相关产品推荐
相关产品推荐

