news 2026/7/29 8:57:02

Docker Compose 前后端部署踩坑实录:3 个坑让我的容器反复 Exit(1)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker Compose 前后端部署踩坑实录:3 个坑让我的容器反复 Exit(1)

技术栈:Vue 3 + Python/Gunicorn + MySQL + Nginx + Docker Compose
部署环境:阿里云 Ubuntu
阅读时长:约 8 分钟


前言

最近在阿里云上用 Docker Compose 部署一个前后端分离项目,前端 Vue 打包后丢给 Nginx,后端是 Python + Gunicorn,数据库 MySQL。

本以为是个常规操作,结果连踩三个坑,容器反复Exited (1),排查了大半天。复盘下来发现这几个问题其实都很典型——尤其是第二个和第三个,属于对 Docker 运行机制理解不到位导致的架构性错误。

记录下来,希望能帮到同样在摸索容器化部署的同学。


坑一:构建产物「人间蒸发」

报错信息

cp: can't stat '/app/dist/*': No such file or directory

现象

执行docker compose up -d,前端镜像构建阶段直接报错,Nginx 启动失败。

根因

Vue 项目在容器内执行npm run build静默失败了。可能的原因包括:

  • 网络超时导致依赖没装全
  • Node 版本和项目不兼容
  • 代码本身有构建错误(TypeScript 类型报错、ESLint 严格模式拦截等)

构建没成功,/app/dist目录压根没生成。到了 Dockerfile 的下一个阶段,COPYcp找不到源文件,直接炸了。

坑在哪?Docker 默认会用缓存,如果之前有一次成功构建的缓存层,后续修改了代码但没清缓存,npm install那一步可能直接跳过,导致新代码跑在旧依赖上,报错信息还特别迷惑。

解法

# 强制重建,不用缓存,完整输出日志dockercompose build --no-cache frontend# 盯住输出里的这些关键词# "ERROR"、"npm ERR!"、"Failed to compile"

看到完整的构建日志后,问题通常一目了然。修完代码再重新构建即可。


坑二:把「构建任务」当成了「服务」

报错信息

Container yunji-frontend-builder Exited (1) service "frontend" didn't complete successfully: exit 1

现象

docker compose build显示✔ Image Built,看起来镜像构建成功了。但docker compose up之后容器状态却是Exited (1)

更诡异的是,容器名字叫yunji-frontend-builder——一个前端服务为什么叫 builder?

根因

这是一个对 Docker Compose 运行机制的根本性误解

当时的docker-compose.yml大概长这样(简化版):

services:frontend:build:./frontendcontainer_name:yunji-frontend-buildercommand:cp-r /app/dist/* /shared/html/volumes:-shared-html:/shared/htmlnginx:image:nginx:alpinevolumes:-shared-html:/usr/share/nginx/htmlports:-"80:80"volumes:shared-html:

思路是:frontend 容器负责构建并把产物复制到共享卷,nginx 容器从共享卷读取文件。

问题在于:Docker Compose 里的每个 service 都是一个长期运行的守护进程。cp命令执行完就退出了,进程结束,Docker 检测到主进程退出,判定服务失败,返回exit 1

这就好比你雇了个人来当前台,结果他把文件放桌上就走了——老板(Docker)觉得他旷工了。

解法

不要在docker-compose.yml里用command跑一次性任务。前端服务应该直接运行 Nginx 守护进程。


坑三:架构冗余——一个前端拆成俩容器

现象

docker compose ps -a一看,出现了两个跟前端相关的容器:

  • yunji-frontend-builder
  • yunji-nginx

它们通过volumes交互:builder 把文件写进共享卷,nginx 从共享卷读。

根因

没用上 Docker 最核心的特性之一——多阶段构建(Multi-stage Build)。

多阶段构建的思路是:在一个 Dockerfile 里定义多个阶段,前面的阶段负责编译/构建,后面的阶段只复制产物,最终镜像里不包含构建工具链,既干净又小。

如果不用多阶段构建,硬要把"构建"和"运行"拆成两个容器,就得靠共享卷来传递文件。这不仅增加了复杂度,还引入了时序依赖(builder 还没跑完,nginx 就启动了怎么办?)、命名混乱、调试困难等一系列问题。

解法

用标准的多阶段构建 Dockerfile,一个服务搞定:

frontend/Dockerfile(修正后):

# ---- 阶段 1:构建 ---- FROM node:18-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # ---- 阶段 2:运行 ---- FROM nginx:alpine # 直接从上一阶段复制产物,不需要共享卷 COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

docker-compose.yml(修正后):

services:frontend:build:./frontendimage:jizhang-frontend:latest# 明确指定镜像名container_name:yunji-frontend# 规范命名,不要叫 builderports:-"80:80"depends_on:-backend# 不写 command,让 Dockerfile 的 CMD 生效# 不挂载 volumes 覆盖 /usr/share/nginx/htmlbackend:build:./backendimage:jizhang-backend:latestcontainer_name:yunji-backendports:-"8000:8000"depends_on:-dbdb:image:mysql:8.0container_name:yunji-dbenvironment:MYSQL_ROOT_PASSWORD:${DB_PASSWORD}MYSQL_DATABASE:jizhangvolumes:-db-data:/var/lib/mysqlvolumes:db-data:

对比修正前后,改动的核心就三点:

  1. 删掉独立的 nginx 服务——前端自己就是 Nginx
  2. 删掉command: cp ...——构建产物在 Dockerfile 里就打包好了
  3. 删掉共享卷——不需要容器间传文件了

排查命令速查表

遇到类似问题时,按这个顺序排查:

构建失败(dist 目录缺失):

# 强制重建,完整日志dockercompose build --no-cache frontend# 关注 "ERROR" 和 "npm ERR!"

容器启动后立即退出:

# 查看所有容器状态(包括已停止的)dockercomposeps-a# 查看容器日志dockercompose logs frontend# 进容器检查文件是否存在dockercompose run--rmfrontendls-la/usr/share/nginx/html

架构/配置问题:

# 检查 compose 配置结构catdocker-compose.yml|grep-A10"services:"# 检查本地镜像列表dockerimages|grepjizhang

踩坑总结

表象本质
构建产物缺失cp: can't stat '/app/dist/*'构建静默失败 + 缓存干扰
容器秒退Exited (1)把一次性命令当守护进程
架构冗余两个容器 + 共享卷没用多阶段构建

回头看这三个问题,其实都指向同一个认知盲区:Docker 不只是"把东西装进容器",它有自己的一套关于进程生命周期和镜像分层的设计哲学。不理解这些,就容易把传统的部署思维(先编译、再拷贝、再启动)原封不动地搬进容器里,造成各种水土不服。

多阶段构建是解决前端容器化部署的标准答案。一个 Dockerfile,两个FROM,干净利落。


分享 AI、Docker、全栈开发的实战经验,把技术讲成人话。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 8:56:26

SpringBoot+Vue协同过滤音乐推荐系统实践

1. 项目概述:基于协同过滤的音乐推荐系统 这个项目构建了一个前后端分离的音乐推荐平台,采用SpringBootVue技术栈实现,核心算法使用协同过滤技术。我在实际开发中发现,这类系统最难的不是基础功能实现,而是如何平衡推荐…

作者头像 李华
网站建设 2026/7/29 8:53:31

Pandas DataFrame创建与动态数据填充:从空表构建到高效操作指南

1. 从“空”开始:为什么需要创建一个空的DataFrame?在数据分析的日常工作中,我们常常会遇到一种场景:数据不是一次性准备好的,而是需要像搭积木一样,一点一点地拼凑起来。比如,你可能正在编写一…

作者头像 李华
网站建设 2026/7/29 8:53:21

HEK293细胞与重组蛋白生产:从基础原理到应用实践

一、HEK293细胞的基础生物学特性HEK293细胞系起源于20世纪70年代,由荷兰科学家Alex Van der Eb实验室通过人胚胎肾细胞与腺病毒5型DNA片段转化而建立。这一独特的起源赋予了该细胞系若干重要的生物学特性:从细胞形态学角度来看,HEK293细胞呈上…

作者头像 李华
网站建设 2026/7/29 8:52:47

STM32驱动SPI彩屏全攻略:从硬件连接到GUI入门

1. 项目概述:从点灯到点亮世界玩STM32的朋友,估计都是从点亮一颗LED开始的。当GPIO的高低电平控制着那颗小灯珠明灭时,那种“我能控制硬件了”的成就感,是驱动我们继续深入的动力。但很快,你会发现,单色的L…

作者头像 李华
网站建设 2026/7/29 8:51:56

C++ RAII与智能指针:从内存管理到工程实践的核心技术

1. 项目概述:从资源管理的“泥潭”到RAII的“救赎”在C的世界里摸爬滚打几年后,你一定会对内存泄漏、文件句柄未关闭、锁未释放这类问题深恶痛绝。新手时期,谁没写过几个new了之后忘了delete的程序,然后看着内存占用曲线一路飙升&…

作者头像 李华