前端必学Docker:从环境一致性到服务器部署的完整实战指南

发布时间:2026/9/19 8:58:48
前端必学Docker:从环境一致性到服务器部署的完整实战指南 1. 前端为什么要碰 Docker先搞清楚它到底解决了什么问题1.1 在我电脑上是好的——环境差异的锅做前端几年你一定听过或者说过这句话代码在我本地跑得好好的怎么到你那儿就不行了 这背后往往是环境不一致的问题Node 版本不同、npm 依赖版本对不上、系统里缺某个底层库、后端接口联调时本地起的服务跟服务器上的配置不一样……排查起来特别费劲而且很多时候复现不了别人的问题。我第一次正经接触 Docker就是因为一个项目在本地开发没问题但部署到测试服务器后页面白屏、接口 502、静态资源 404各种怪问题轮着来。后来发现是服务器上 Node 版本太老构建出来的产物跟本地不一致加上 nginx 配置踩了 SPA 路由的坑。折腾了一整天当时就想如果能像搬箱子一样把整个环境一起搬过去该多好。Docker 干的就是这件事它把代码 运行环境 配置打包成一个标准化的箱子搬到哪都能跑不再依赖目标机器上装了什么东西。这篇文章我打算从前端开发者的视角把 Docker 从零开始讲明白一路讲到把前端项目真正部署到服务器上形成一个完整的闭环。内容不涉及太深的后端知识所有概念我都会用前端熟悉的东西做类比保证你学完能自己上手操作。1.2 Docker 到底是个啥用前端的话说清楚你可以把 Docker 理解成一个快递打包系统。我们平时发 npm 包是把代码和依赖打包别人下载后能不能跑取决于他的 Node 环境而 Docker 更进一步它把代码 依赖 操作系统层面对应的运行环境整个打包成一个镜像。镜像到了任何装了 Docker 的机器上都能用一模一样的方式启动环境差异被彻底抹平。更直白的类比是这样你写了一个 Vue 项目本机能跑是因为你装好了 Node、npm、可能还有各种全局工具。服务器上要跑同一个项目理论上也得装一模一样的 Node 版本、一模一样的全局依赖还得配好 nginx、设置好环境变量。这一步一步手工操作只要漏一个细节项目就起不来。Docker 的做法是把这些安装和配置过程全部写进一个 Dockerfile 文件然后构建成一个镜像。以后不管在哪台服务器上一条 docker run 命令就能把整套环境启动起来而且保证和你本地构建时完全一致。Docker 的核心优势对前端来说主要有三个一是环境一致性本地怎么跑服务器就怎么跑二是部署简单一条命令搞定不用每次上线前写一堆操作文档三是隔离干净一个容器一个项目互不干扰就算服务器上同时跑十几个前端项目也不会因为依赖版本不同而打架。1.3 前端学 Docker 的收益边界我不建议前端把 Docker 学成运维专家但有几个场景是实实在在需要用到的第一种项目要部署到云服务器无论是阿里云、腾讯云还是其他平台用 Docker 部署会比手动装环境省心太多第二种公司内部有多个前端项目共用一个服务器用容器隔离后互不影响第三种本地开发时需要依赖一些中间件比如 Redis、MySQL、Nginx用 Docker 起一个容器比在本机装原生服务干净得多用完删掉不会污染系统。这篇文章就是围绕前端项目容器化 服务器部署这条主线来写的。我会先把概念讲透再带你写一个真实的 Dockerfile把 Vue 或 React 项目构建成镜像最后推送到服务器上跑起来并且把更新、日志、数据持久化这些实际部署中一定会遇到的问题也讲清楚。学完这套你不仅能应付面试里常见的 Docker 问题更能真正独立完成一个项目的上线。2. 核心概念镜像、容器、仓库别被术语吓住2.1 镜像Image相当于一个环境的 npm 包镜像这个概念用前端的话说就是一个包含了完整运行环境的只读模板。它不只是你的代码还包括了操作系统的基础层、Node 运行时、nginx、项目依赖、配置文件等等。你可以把镜像理解成 npm 包只不过这个包不是只有 JS 代码而是连运行它的宿主环境一起打包了。拿 npm 包类比你用 npm install 装了一个包这个包有版本号比如 vue3.4.0有依赖关系有固定的文件结构Docker 镜像同样有 tag 做版本管理比如 node:20-alpine、nginx:1.25-alpine有继承关系一个镜像可以基于另一个镜像扩展有固定的分层结构。镜像本身是只读的你不能直接改一个镜像里的文件只能基于它创建容器后再改或者重新构建一个新镜像。看一个最简单的镜像示例这就是前端项目最常用的 node 镜像FROM node:20-alpine这行代码的意思是基于 node 20 的 alpine 版本镜像来构建。alpine 是一个特别精简的 Linux 发行版镜像体积小只有几 MB 到几十 MB比完整的 Ubuntu 镜像小很多用来做前端构建非常合适。这个基础镜像里已经装好了 Node、npm你不需要再手动去安装。2.2 容器Container跑起来的实例如果说镜像是类Class那容器就是实例Instance。这个类比前端同学应该秒懂类定义了属性和方法实例才是真正在内存里跑起来的对象。镜像是静态的模板文件容器是镜像运行后的进程它有独立的文件系统、网络、进程空间里面可以跑命令、写文件、起服务。你可以在同一个镜像基础上启动多个容器它们相互隔离互不影响。比如同一个 nginx 镜像可以启动两个容器一个跑你的前端项目 A一个跑项目 B端口映射不同就行。容器可以被启动、停止、删除删除后容器里产生的数据如果没有挂载数据卷会跟着丢失但镜像不受影响随时可以再创建新容器。我习惯把容器理解为一个轻量级的虚拟机但比虚拟机轻得多。虚拟机要跑一整个操作系统启动可能要几十秒到几分钟容器直接共享宿主机的操作系统内核启动只需要几百毫秒到一两秒。这也是为什么 Docker 能在一台服务器上同时跑几十个容器而不太吃力。2.3 仓库Registry镜像的 npm registry镜像构建好之后要让人家能用就得放到一个镜像仓库里类似 npm registry。Docker 官方的仓库叫 Docker Hub里面有很多官方维护的基础镜像比如 node、nginx、mysql、redis这些镜像质量高、更新及时基本能满足绝大多数需求。我们自己构建的前端项目镜像可以推到 Docker Hub 的公开仓库也可以推到私有仓库。国内常见的私有仓库有阿里云容器镜像服务ACR、腾讯云 TCR也可以自己在服务器上用 Registry 镜像搭建一个简单的私有仓库。生产环境我强烈建议用私有仓库因为前端项目镜像里可能包含一些不太适合公开的配置信息。拉取镜像的命令和 npm install 很像# npm install vue docker pull node:20-alpine # npm install 指定版本 docker pull nginx:1.25-alpine2.4 数据卷与网络两个绕不开的概念数据卷Volume是 Docker 里实现数据持久化和宿主机与容器间数据共享的机制。容器被删除后容器内的文件就没了但如果你把某个目录挂载成数据卷数据就会存到宿主机上容器删了数据还在。比如 nginx 容器里的日志目录或者前端项目运行时产生的上传文件都应该挂载到宿主机上。网络这块前端只需要理解两件事端口映射和容器间通信。端口映射是把容器的某个端口映射到宿主机的某个端口比如把容器里的 nginx 80 端口映射到宿主机的 8080 端口这样外部访问http://服务器IP:8080就能打到容器里的 nginx。容器间通信则是让多个容器可以通过内部网络互相访问比如前端容器要访问后端容器可以不用暴露端口到公网直接在同一个 Docker 网络里通过容器名访问这样更安全。3. 环境准备本地开发机和服务器都要装好 Docker3.1 本地安装 Docker DesktopWindows 和 macOS 的注意事项本地开发环境推荐直接用 Docker Desktop它自带图形界面能直观地看到镜像、容器、数据卷的状态对新手特别友好。Windows 用户要注意Docker Desktop 依赖 WSL 2Windows Subsystem for Linux或者 Hyper-V安装前最好先确认 Windows 的虚拟化功能已经开启。最容易踩的坑是启动时报错 virtualization support not detected 或者 Virtualization support wasnt detected。遇到这个报错先去 BIOS 里把 Intel VT-x 或者 AMD-V 打开然后在 Windows 功能里确认虚拟机平台和适用于 Linux 的 Windows 子系统这两个选项已勾选再执行wsl --update更新一下 WSL 内核重启后一般就能解决。macOS 用户装 Docker Desktop 相对简单但要注意芯片类型Apple SiliconM1/M2/M3和 Intel 芯片需要下载不同版本的安装包。装完之后在终端跑一下docker version docker compose version如果两个命令都能正常输出版本信息说明 Docker 已经装好了。Docker Desktop 默认会自动启动你也可以手动点开它的面板查看当前运行的容器和镜像。3.2 Linux 服务器安装用包管理器还是官方脚本服务器的安装方式跟本地不一样。国内云服务器大多是 CentOS、Ubuntu 或者 Debian我建议直接跟着 Docker 官方文档用包管理器安装这样版本比较新维护也方便。以 Ubuntu 为例# 更新 apt 包索引 sudo apt-get update # 安装依赖包让 apt 可以通过 HTTPS 访问 Docker 官方仓库 sudo apt-get install -y ca-certificates curl # 添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod ar /etc/apt/keyrings/docker.asc # 添加 Docker 仓库到 apt 源 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 正式安装 Docker sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin装完之后记得把当前用户加入 docker 组这样就不用每次敲 sudo 了sudo usermod -aG docker $USER newgrp dockerCentOS 的安装步骤类似只是包管理器从 apt 换成了 yum/dnf。如果你不想折腾这些也可以直接用官方的一键安装脚本但我不推荐在生产服务器上这么干因为脚本不够透明你没法确认它到底执行了哪些操作。只有一条命令这个说白了就是两条路的事看你自己习惯。3.3 配置镜像加速在国内拉镜像不被卡住的正确姿势服务器和本地装好 Docker 后第一个要解决的问题就是镜像拉取速度。Docker Hub 官方源在国内访问速度很慢有时候一个 node 镜像能拉十几分钟甚至超时。解决办法是配置国内镜像加速器把 registry 地址指向国内服务商提供的加速节点。常见加速器地址有好几个具体用哪个看你的网络环境。我用的是阿里云的容器镜像加速服务登录容器镜像服务控制台能看到每个人专属的加速地址安全性比较高。配置方法是编辑/etc/docker/daemon.json{ registry-mirrors: [https://你的专属加速地址.mirror.aliyuncs.com] }修改完后重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker然后拉一个镜像测试速度docker pull nginx:alpine如果速度明显变快说明配置生效了。这里有个小细节daemon.json 这个文件如果不存在需要手动创建注意 JSON 格式一定要正确否则 Docker 会启动失败。配置完成后可以用docker info命令确认镜像加速器是否生效在输出里能看到 Registry Mirrors 列表。4. 写 Dockerfile把 Vue/React 项目打包成前端镜像4.1 从前端构建到镜像构建的思路转变本地跑一个前端项目流程通常是这样先npm install装依赖再npm run build构建出 dist 静态文件然后用 nginx 指向 dist 目录或者用 Node 起一个静态服务器。镜像构建的逻辑其实一模一样只是把这些步骤固化到 Dockerfile 里让 Docker 自动执行。我第一次写 Dockerfile 时犯过的错是把npm install和npm run build放在同一个阶段而且没有利用好镜像缓存每次改一行代码都要重新npm install构建一次要等好几分钟。后来才意识到Docker 的每个 RUN 指令都会生成一个新的镜像层如果前面的层没变Docker 会直接复用缓存。所以正确做法是先把 package.json 和 package-lock.json 复制进去单独跑一次npm install再复制源代码。这样只要依赖没有变化以后构建都会直接命中缓存速度会快很多。来看一个基础的 Dockerfile# 第一阶段构建前端静态文件 FROM node:20-alpine AS builder WORKDIR /app # 先复制依赖清单利用 Docker 缓存 COPY package.json package-lock.json ./ RUN npm install # 再复制源码并构建 COPY . . RUN npm run build # 第二阶段用 nginx 托管静态文件 FROM nginx:1.25-alpine COPY --frombuilder /app/dist /usr/share/nginx/html4.2 多阶段构建一个 Dockerfile 搞定构建和运行上面这个 Dockerfile 用了多阶段构建这是前端镜像推荐的标准姿势。核心思路是第一阶段用 node 镜像安装依赖并构建第二阶段用 nginx 镜像只拷贝构建产物。这样做的好处是最终生成的镜像体积非常小——node 镜像动辄几百 MB而 nginx:alpine 只有几十 MB而且镜像是精简的系统不会带上 npm 缓存和 node_modules 这些构建过程中的垃圾文件。你可以用docker build命令构建镜像# 在项目根目录执行 docker build -t my-vue-app:1.0.0 .注意结尾有一个点表示构建上下文是当前目录。Docker 会把当前目录下的所有文件都发送给 Docker 引擎来做构建所以一定要在项目根目录加一个.dockerignore文件把 node_modules、dist、.git 这些不需要的文件排除掉不然镜像构建会非常慢甚至会把本地的 node_modules 一起打包进去。.dockerignore文件内容大致这样node_modules dist .git *.log .DS_Store4.3 nginx 配置解决 SPA 路由刷新 404 的问题直接用默认 nginx 镜像托管 dist 有一个很常见的问题Vue 或 React 的 history 路由模式在页面内跳转没问题但一刷新或者直接访问某个子路由nginx 会返回 404。原因很简单nginx 在磁盘上找不到/detail这个文件就返回 404 了。解决办法是配置 nginx 的 try_files 指令把前端路由的请求全部回退到 index.html。新建一个nginx.conf文件server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # 所有请求都回退到 index.html交给前端路由处理 location / { try_files $uri $uri/ /index.html; } # 静态资源缓存提升性能 location /assets/ { expires 30d; add_header Cache-Control public, no-transform; } # 前端项目里的 favicon 或者其他静态文件 location /favicon.ico { log_not_found off; access_log off; } }修改 Dockerfile把自定义的 nginx 配置拷贝进去FROM nginx:1.25-alpine COPY --frombuilder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf4.4 构建镜像并本地验证跑起来看效果Dockerfile 写好后在本地先构建并运行容器验证一下# 构建镜像 docker build -t my-vue-app:1.0.0 . # 运行容器把 80 端口映射到本地 8080 docker run -d -p 8080:80 --name my-app my-vue-app:1.0.0打开浏览器访问http://localhost:8080如果能看到你的前端页面而且刷新子路由不会 404说明镜像构建成功。验证完可以停掉容器docker stop my-app docker rm my-app一个小提醒docker run的-d参数表示后台运行这样终端不会有日志刷屏--name给容器起个名字方便后续管理。如果你想看实时日志可以docker logs -f my-app。5. docker run 实战端口映射、日志、进入容器一条条讲透5.1 前端最常用的 Docker 命令没有之一很多人觉得 Docker 命令多记不住其实前端开发真正常用的就那么几条。先明确一下命令体系docker ps看当前运行的容器docker images看本机所有的镜像docker pull拉镜像docker run运行容器docker build构建镜像docker logs看容器日志docker exec进入容器执行命令。记住这七条就够起步了。新手最容易混淆的是run、start和restart。docker run是根据镜像创建一个全新的容器并启动docker start是启动一个已经存在但处于停止状态的容器docker restart是重启一个正在运行的容器。你可以用docker ps -a看到所有容器包括已经停止的docker ps默认只显示正在运行的。5.2 端口映射到底怎么理解端口映射是部署中最关键的一环理解了它你就理解了一大半的网络配置。容器内部的服务监听某个端口比如 nginx 默认监听 80但这个 80 是容器内部的 80宿主机并不知道。为了让外部能访问到必须在docker run时指定映射关系docker run -d -p 8080:80 my-vue-app:1.0.0-p 8080:80的意思是把宿主机的 8080 端口映射到容器的 80 端口。外部访问http://服务器IP:8080时流量会转发到容器内的 80 端口由 nginx 接收并响应。宿主机端口和容器端口可以是同一个数字比如-p 80:80但要注意宿主机端口不能冲突两台容器不能映射同一个宿主机端口。有一次我在服务器上同时跑两个前端项目先跑了一个映射到 8080第二个也想用 8080结果报错 port is already allocated。这就是端口冲突。解决办法是给第二个项目换一个宿主机端口比如 8081或者是把容器放在 Docker 网络里通过容器名互相访问不暴露到宿主机端口这种方式更适合前后端联调场景。5.3 查看日志和进入容器排查问题的基本动作容器跑起来后问题排查主要靠两条路看日志和进容器里看现场。看日志很简单# 查看最近 100 行日志 docker logs --tail 100 my-app # 实时跟踪日志输出类似 tail -f docker logs -f my-app如果容器能正常运行但页面有异常可以进入容器内检查# 进入容器起一个交互式 shell docker exec -it my-app sh # 进入后可以看目录结构、检查配置文件 ls /usr/share/nginx/html cat /etc/nginx/conf.d/default.conf注意alpine 系统的镜像没有 bash只有 sh所以命令是docker exec -it my-app sh。如果你想看正在运行的前端页面有没有被打包进去看/usr/share/nginx/html目录下是否有 index.html 和 assets 文件夹就行了。这些操作在 Docker Desktop 的图形界面里也能做但命令行更高效服务器上也只能用命令行所以还是尽早习惯比较好。6. 服务器部署闭环从本地镜像到线上服务6.1 方案选型镜像怎么搬到服务器上本地构建好的镜像要部署到服务器主要有三种思路。第一种把镜像推送到镜像仓库服务器再从仓库拉取这是生产环境最推荐的方式第二种把项目代码复制到服务器在服务器上直接构建镜像适合个人开发小项目第三种用docker save把镜像导出成 tar 文件拷贝到服务器后docker load导入适合内网环境或者离线部署。我实际用得最多的是第一种本地构建并推送到阿里云容器镜像服务然后在服务器上拉取并运行。这个流程看起来步骤多但好处是版本管理清晰、回滚方便而且只要服务器能访问到镜像仓库不管有多少台服务器都能拉同一份镜像保证所有环境一致。如果你不想用云厂商的镜像仓库也可以自己搭一个私有的 Registry。在服务器上运行docker run -d -p 5000:5000 --name registry registry:2这个命令会启动一个极简的私有镜像仓库监听 5000 端口。本地构建镜像后把镜像 tag 改成服务器IP:5000/项目名:版本号然后推送到这个私有仓库。这种方式适合对数据安全要求高的内网场景但要注意默认的 Registry 没有鉴权暴露在公网有风险所以如果要公网访问需要加 TLS 和密码认证。6.2 推送镜像到远程仓库打 tag 和 push 的完整流程以阿里云容器镜像服务为例其他平台类似流程是这样的先在控制台创建命名空间和镜像仓库然后在本地给镜像打 tag最后推送。# 登录阿里云 Docker Registry替换成你自己的地址 docker login --username你的账号 registry.cn-hangzhou.aliyuncs.com # 给本地镜像打 tag格式是 仓库地址/命名空间/镜像名:版本号 docker tag my-vue-app:1.0.0 registry.cn-hangzhou.aliyuncs.com/my-namespace/my-vue-app:1.0.0 # 推送镜像 docker push registry.cn-hangzhou.aliyuncs.com/my-namespace/my-vue-app:1.0.0推送的时候有一点要注意镜像的 tag 就相当于版本号我用的是语义化版本规则1.0.0是大版本1.0.1是小修复latest则是最新快照。在实际项目里我倾向于把一个稳定版本同时打上具体版本号和latest这样服务器拉取时既可以选择固定版本保证稳定也可以用latest跟上最新改动。6.3 服务器拉取并运行从零到线上服务镜像推送到仓库后在服务器上只需要三步登录、拉取、运行。# 登录镜像仓库 docker login --username你的账号 registry.cn-hangzhou.aliyuncs.com # 拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/my-namespace/my-vue-app:1.0.0 # 运行容器 docker run -d \ --name my-vue-app \ -p 80:80 \ --restart always \ registry.cn-hangzhou.aliyuncs.com/my-namespace/my-vue-app:1.0.0--restart always这个参数特别关键它的意思是容器意外退出时自动重启。服务器重启后Docker 也会自动把这个容器拉起来不用你手动干预。如果不加这个参数服务器一断电重启你的服务就再也起不来了得手动docker start这在生产环境是不可接受的。运行之后用docker ps确认容器状态然后在浏览器访问http://服务器IP如果能看到页面恭喜你闭环已经完成了。6.4 用 docker-compose 编排多服务再加一个容器也不乱当项目变多或者你需要同时跑前端、后端、数据库三个容器时一条条写docker run命令就会很痛苦。这时候用docker compose把你需要启动的所有容器定义在一个 YAML 文件里一条命令全部启动。以前端 后端为例创建一个docker-compose.ymlversion: 3.8 services: frontend: image: registry.cn-hangzhou.aliyuncs.com/my-namespace/my-vue-app:1.0.0 ports: - 80:80 restart: always backend: image: registry.cn-hangzhou.aliyuncs.com/my-namespace/my-api:1.0.0 ports: - 3000:3000 restart: always用法也很简单# 启动所有服务 docker compose up -d # 查看服务状态 docker compose ps # 停止并移除所有服务 docker compose down前端容器要访问后端容器时不用通过localhost:3000直接通过服务名backend:3000访问即可。Docker 会为 compose 项目创建一个内部网络服务名就是容器在网络中的域名。这个特性在前后端联调时非常实用不用再关心 IP 地址网络配置也安全得多。注意backend的服务名要尽量只用字母和数字避免下划线导致 DNS 解析问题。7. 部署之后的日常工作日志、持久化、版本更新7.1 发布新版本的正确姿势零停机更新项目上线后你肯定要持续迭代。最常见的问题是怎么发布新版本不影响用户访问如果直接停掉旧容器再启动新容器中间会有几秒到十几秒的空窗期页面打不开。对于前端项目来说简单方案是用docker compose完成滚动更新docker compose up -d --build如果用了 compose它会自动检测到镜像版本变化先启动新容器再停掉旧容器。如果新容器启动失败它会回滚到旧版本这个过程对用户基本无感。如果不用 compose就得手动操作先docker pull新镜像再docker stop旧容器docker rm移除旧容器最后docker run新容器。顺序错了或漏掉一步服务就可能挂掉。我踩过的坑是旧容器还没删掉新容器因为同名冲突启动失败。所以如果不用 compose务必把停止、删除、启动三步按顺序执行完。实际发布流程中我建议先在本地构建镜像并本地起容器验证一下再推送到仓库最后在服务器上拉取更新。避免把一个有问题的版本直接推到生产环境。7.2 数据卷挂载日志和上传文件不能丢容器是临时的删除容器后里面的文件就没了。但生产环境里很多数据是不能丢的。对前端项目来说最常见的是两类nginx 的访问日志和应用产生的上传文件。解决办法是挂载数据卷Volume把容器里的目录映射到宿主机上。用-v参数启动容器docker run -d \ --name my-vue-app \ -p 80:80 \ -v /data/my-vue-app/logs:/var/log/nginx \ -v /data/my-vue-app/uploads:/usr/share/nginx/html/uploads \ --restart always \ registry.cn-hangzhou.aliyuncs.com/my-namespace/my-vue-app:1.0.0这里的两个-v参数分别把 nginx 日志目录和上传目录挂载到宿主机的/data/my-vue-app下。这样即使容器被删掉重建日志还在、上传文件还在不会丢数据。数据卷目录的权限也需要注意宿主机目录的属主和容器内的用户 id 可能需要保持一致否则容器写文件时可能报 Permission denied。7.3 容器重启策略让服务自己恢复生产环境还有一个常见场景服务器内存不足导致某个容器被杀或者代码里有内存泄漏导致进程崩溃。如果没有设置重启策略容器挂了就挂了直到你发现用户访问不了才去处理。有了--restart alwaysDocker 会自动把容器拉回来。Docker 支持多种重启策略no是不自动重启always是任何情况都重启包括 Docker 服务重启后也自动拉起容器on-failure是在容器非正常退出时才重启还可以加次数限制on-failure:3。在docker-compose.yml里对应配置是services: frontend: image: registry.cn-hangzhou.aliyuncs.com/my-namespace/my-vue-app:1.0.0 ports: - 80:80 restart: always我个人的建议核心服务用always辅助服务像日志收集这种用unless-stopped。unless-stopped和always的区别是如果管理员手动停止了容器unless-stopped不会在重启 Docker 时自动拉起来always会。这个细节面试也常考记下来没坏处。8. 常见问题与排查技巧实录8.1 容器一直重启Restarting从日志里找突破口部署后最常见的故障就是容器反复处于 Restarting 状态。你用docker ps会看到容器 STATUS 列显示 Restarting (1) 5 seconds ago蒙了。原因五花八门但排查方向很固定直接看日志绝大多数问题一眼就能定位。docker logs my-app常见的原因有这么几类一是 nginx 配置写错了比如缩进问题、少写分号nginx 启动失败容器反复重启二是端口被宿主机上其他进程占了容器启动时绑定端口失败三是镜像本身有问题比如复制进去的静态文件不完整nginx 找不到 index.html。每种问题日志里都会有明显提示比如 address already in use 说明端口冲突nginx: configuration failed 说明配置写错了。日志看完还解决不了可以docker inspect my-app查看容器详情重点看 State、ExitCode、Error 这几个字段。8.2 镜像拉不下来加速器失效和 DNS 问题国内服务器上经常遇到镜像拉不下来的情况特别是重启了一台新服务器忘了配置加速器。症状是 pull 的时候卡在 Waiting 或者超时。先检查一下/etc/docker/daemon.json是否存在、格式对不对然后docker info确认加速器列表。还有一种情况是配置好了加速器但依然慢可能是 Daemon 没有重启成功配置文件 JSON 语法错误会导致 Docker 直接拒绝启动。验证方法很简单改完配置后执行sudo systemctl restart docker然后立刻docker ps如果命令报错大概率是配置文件有问题。把 daemon.json 临时移走再重启就能确认。8.3 端口映射访问不了防火墙、安全组和监听地址镜像拉下来、容器也起来了、日志正常但浏览器就是访问不了。这种问题九成不是 Docker 的问题 而是云服务器的安全组策略。很多云厂商默认只开放少数端口你要在控制台的安全组规则里放行宿主机端口比如 8080。Ubuntu 服务器上还有本机防火墙 ufw用sudo ufw status查看如果防火墙在启用状态需要sudo ufw allow 80/tcp。另一个容易忽略的点是容器内的监听地址。如果容器内的服务监听的是127.0.0.1就只能容器内部访问要对外提供服务得监听0.0.0.0或者::。nginx 默认监听所有地址一般不会踩这个坑但如果你用 Node 直接起服务app.listen(3000)默认监听所有地址换成app.listen(3000, 127.0.0.1)就只能在容器内访问映射到宿主机也访问不了。排查这种问题可以在宿主机上curl http://localhost:8080如果宿主机能通而外网不能通那就是防火墙或安全组的问题。8.4 容器时区不对日志时间慢了 8 小时容器默认使用 UTC 时区而我们习惯的是北京时间UTC8。如果你看容器日志发现时间总是比实际慢 8 小时这是正常现象不是 Bug。解决方法是启动容器时挂载宿主机的时区文件docker run -d \ --name my-vue-app \ -p 80:80 \ -v /etc/localtime:/etc/localtime:ro \ -v /etc/timezone:/etc/timezone:ro \ my-vue-app:1.0.0或者更规范的做法是在 Dockerfile 里设置时区环境变量ENV TZAsia/Shanghai8.5 镜像体积太大构建产物臃肿的优化思路前端项目的镜像体积如果超过 200MB基本可以断定构建阶段有问题。我见过有的同事npm install之后没删掉缓存构建 mirror 里包含 devDependencies还有的人直接把整个项目目录 COPY 进去再在容器里 install导致镜像里塞进了 node_modules、源码、构建配置一大堆东西。优化思路就三招一是用多阶段构建最终镜像只保留构建产物二是.dockerignore一定要写对排除 node_modules 和 dist三是基础镜像用 alpine 版本能省一半体积。举个实际数据我用node:20-alpine构建一个 Vue 项目最终镜像大约 40MB 左右如果用完整版 node 镜像做最终阶段动辄 1GB。这个优化对磁盘和拉取速度的影响非常明显。9. 写在最后前端把容器思维带进日常开发写这篇内容的时候我一直在想一个问题前端为什么要学 Docker不是为了卷而是因为工程化发展到今天前端不再只是写页面还承担了部署、联调、环境管理这些事情。Docker 提供了一个很好的抽象让你把环境也当成代码来管理。这种思维方式的转变比记住几十条命令重要得多。我自己从能用 docker run 跑通 demo到能独立上线生产项目中间踩了不少坑最大的体会是先在低风险项目里多练把流程跑顺了再去碰生产环境。例如接手一个简单的活动页或者内部后台从 Dockerfile 到服务器部署完整走一遍你会发现自己对前端工程化的理解会深一个层次。踩过的坑越多后面越稳遇到 Exited (1) 这种报错也不用慌一条docker logs就能让问题水落石出。最后再分享一个小技巧如果你在公司本地开发环境配了 Docker Desktop试着把 Redis、MySQL 这些中间件全部用 Docker 跑起来不用的时候一键停掉系统干净不少。从这些小场景开始用起你自然会感受到容器化的便利后面再碰到部署问题时就不会觉得 Docker 是个陌生工具了。