Docker 前端本地开发指南(实操版)
本文只讲一件事:怎么用 Docker 把前端开发环境跑起来,并且能正常热更新。
如果你是想系统学习 Docker(生产部署、镜像仓库、K8s 等),请看完整版《Docker 开发与部署指南》;本文是它的"本地开发子集"------把日常开发真正用得上的部分提炼出来,砍掉所有延伸内容,照着做就行。
目录
- [一、为什么要用 Docker 跑前端开发](#一、为什么要用 Docker 跑前端开发 "#%E4%B8%80%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8-docker-%E8%B7%91%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91")
- 二、核心概念速览(够用就行)
- 三、环境准备
- 四、三个核心文件
- 五、第一次启动(初始化)
- 六、日常开发流程
- 七、热更新:正确姿势与原理(重点)
- 八、常见问题排查
- [九、Docker Desktop 图形界面日常操作](#九、Docker Desktop 图形界面日常操作 "#%E4%B9%9Ddocker-desktop-%E5%9B%BE%E5%BD%A2%E7%95%8C%E9%9D%A2%E6%97%A5%E5%B8%B8%E6%93%8D%E4%BD%9C")
- 十、快捷命令速查
- [十一、两个项目的配置对照(vue3 / uniapp)](#十一、两个项目的配置对照(vue3 / uniapp) "#%E5%8D%81%E4%B8%80%E4%B8%A4%E4%B8%AA%E9%A1%B9%E7%9B%AE%E7%9A%84%E9%85%8D%E7%BD%AE%E5%AF%B9%E7%85%A7vue3--uniapp")
- 附录:常用命令速查表
一、为什么要用 Docker 跑前端开发
| 痛点 | Docker 解决方案 |
|---|---|
| 团队成员 Node 版本不一致 | 镜像锁定版本,人人一样 |
| Windows 装某些 npm 包失败(如 node-sass) | 容器内是 Linux,原生兼容 |
| 新人入职装环境要半天 | docker compose up 一条命令搞定 |
| 切换项目时依赖冲突 | 每个项目独立容器,互不干扰 |
一句话:把 Node 版本、依赖、启动命令全部固化进镜像,任何人、任何机器,一条命令拉出完全一致的开发环境。
二、核心概念速览(够用就行)
本地开发只需要理解三个词:
| 概念 | 通俗理解 | 本地开发里对应什么 |
|---|---|---|
| 镜像(Image) | 应用的"安装包",只读模板 | node:20-alpine + 你的项目依赖 |
| 容器(Container) | 镜像跑起来的"活实例" | 正在跑的 vite dev server |
| 数据卷(Volume) | Docker 管理的持久化存储 | node_modules、数据库数据 |
三者关系:
markdown
Dockerfile ──build──> Image(安装包)
│
│ docker compose up
▼
Container(活实例)
│
│ 挂载
▼
Volume(持久化数据)
本地开发只需要记住:容器 = 你项目的"独立运行环境",删了可以随时重建,不影响你的源码和依赖。
三、环境准备
3.1 安装 Docker Desktop
Windows 上装 Docker Desktop,安装后启动即可(默认用 WSL2 后端)。
3.2 配国内镜像加速(强烈建议)
国内直接访问 Docker Hub 经常超时,先配加速器:
Docker Desktop → Settings → Docker Engine:
json
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com",
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
]
}
Apply & Restart 生效。
3.3 确认后端地址
前端开发时请求要转发到后端(yudao-server)。两种常见情况:
| 后端在哪 | 前端配置 |
|---|---|
| 宿主机 IDE 里跑 | http://host.docker.internal:48080 |
| 局域网某台机器跑 | 直接写 IP,如 http://192.168.1.182:48080 |
这个地址通过 compose 的 environment 注入(见下文 4.2),不用改代码。
四、三个核心文件
每个前端项目目录下都要有这三个文件。以 yudao-ui-admin-uniapp 为例(vue3 项目的差异见第十一章)。
4.1 Dockerfile.dev
dockerfile
FROM node:20-alpine
# git 被 husky 间接需要;corepack 启用 pnpm@10
RUN apk add --no-cache git && corepack enable
WORKDIR /app
# 只拷依赖描述文件,利用 docker 层缓存(改代码不会触发重装依赖)
COPY package.json pnpm-lock.yaml .npmrc ./
# --ignore-scripts: 跳过 prepare 钩子(scripts/ 目录此时还没拷进来)
RUN pnpm install --frozen-lockfile --ignore-scripts
# 源码打进镜像(配合 compose watch 的 sync 实现热更新,见 4.2)
COPY . .
EXPOSE 9000
# 默认 H5 开发模式
CMD ["pnpm", "dev:h5"]
两个关键点:
pnpm install只拷了依赖描述文件 → 依赖装进镜像层,改代码不会重装;COPY . .把源码拷进镜像 → 不能删,热更新方案靠它(见第七章)。
4.2 docker-compose.dev.yml
yaml
services:
uniapp-dev:
build:
context: .
dockerfile: Dockerfile.dev
container_name: uniapp-dev
ports:
- "9000:9000"
develop:
watch:
# 源码改动:仅同步进容器,Vite 原生 HMR(保留页面状态)
- action: sync
path: ./src
target: /app/src
- action: sync
path: ./index.html
target: /app/index.html
# 根目录配置文件改动:需重启 vite(配置无法热加载)
- action: sync+restart
path: ./vite.config.ts
target: /app/vite.config.ts
# 依赖改动:重建镜像
- action: rebuild
path: ./package.json
- action: rebuild
path: ./pnpm-lock.yaml
environment:
# 后端地址(见 3.3)
- VITE_SERVER_TARGET=http://host.docker.internal:48080
extra_hosts:
- "host.docker.internal:host-gateway"
stdin_open: true
tty: true
develop.watch 三种动作的含义:
| 动作 | 触发时机 | 效果 |
|---|---|---|
sync |
源码文件(src/)变化 |
只把文件同步进容器,Vite 原生 HMR,不重启 |
sync+restart |
配置文件(vite.config.ts 等)变化 |
同步后重启 vite(配置必须重启才生效) |
rebuild |
package.json / pnpm-lock.yaml 变化 |
重新构建镜像(依赖变了,要重装) |
4.3 .dockerignore
dockerignore
node_modules
dist
.git
docs
.image
.husky
.vscode
.github
*.log
排除无关文件,缩小构建上下文、加快构建。
五、第一次启动(初始化)
bash
cd yudao-ui-admin-uniapp
docker compose -f docker-compose.dev.yml up --watch
或(项目里已封装好脚本):
bash
pnpm dev:docker
首次启动会做三件事:拉基础镜像 → 构建项目镜像(装依赖)→ 启动容器并进入 watch 模式。构建需要几分钟,之后启动就是秒级。
启动成功后,日志出现 vite 的 ready in xxx ms,浏览器访问:
bash
http://localhost:9000/admin-ui-uniapp/
注意:命令里的
--watch不能省,这是热更新的关键(见第七章)。
六、日常开发流程
6.1 每天开工
bash
cd yudao-ui-admin-uniapp
pnpm dev:docker
6.2 改代码 → 自动热更新
在 IDE 里正常改 src/ 下的代码,保存即生效:
- 改组件/页面 → Vite 原生 HMR,毫秒级,保留页面状态(不刷新页面);
- 改
vite.config.ts等配置文件 → 自动重启 vite(约 2~5 秒); - 改
package.json依赖 → 自动重建镜像(需要网络)。
6.3 收工
bash
# 停止容器(保留镜像和数据,下次秒启)
docker compose -f docker-compose.dev.yml stop
# 或直接 Ctrl+C(up --watch 在前台跑时)
6.4 什么时候需要重建
| 场景 | 命令 |
|---|---|
| 改代码(日常) | 什么都不用做,自动热更新 |
改 Dockerfile.dev |
docker compose -f docker-compose.dev.yml up --watch --build |
| 改 compose 文件 | 重新 up --watch |
| 改依赖(package.json) | 自动 rebuild,或手动 --build |
七、热更新:正确姿势与原理(重点)
7.1 症状
Windows + Docker Desktop 下,如果按网上教程用 bind mount (volumes: - .:/app)方式挂源码,改代码后浏览器不刷新,必须重启容器才生效。这是 Windows 上 Docker 跑前端最常见的坑。
7.2 根因
Docker Desktop 在 Windows 上通过 WSL2/Hyper-V 的虚拟文件共享层 把宿主机目录挂进容器。这个共享层不会把宿主机侧的文件变更事件转发进容器------容器内的 Vite/chokidar 收不到"文件变了"的通知,HMR 自然不触发。
bash
bind mount 方案:
宿主机改代码 → 文件在宿主机侧变化
│
▼
虚拟文件共享层 ⚠️ 变更事件在这里丢失
▼
容器内 Vite 收不到通知 → HMR 不触发
7.3 正确方案:COPY + compose watch 的 sync
思路 :不靠 bind mount,改为「源码 COPY 进镜像 + compose watch 在宿主机侧 监听文件、把改动写进容器」。文件是在容器侧写入的(本地写入),Vite 的原生文件监听能正常收到事件,原生 HMR 即可工作。
bash
正确方案:
宿主机改代码
│ compose watch 在宿主机侧监听(Windows 原生监听,正常)
▼
sync 把新文件写进容器(容器侧写入,事件正常)
▼
Vite 原生 HMR 检测到变化 → 模块热替换(保留状态,毫秒级)
这就是第四章那份配置的来源 :Dockerfile.dev 里 COPY . . 打源码,compose 里 src 用 sync。
7.4 为什么必须 up --watch,点 Start 不行
Docker Desktop 的 Start 按钮只是 docker start(拉起容器进程),不会启动 compose watch 的宿主机侧监听会话 。没有 watch 会话就没有 sync,容器里跑的是镜像里的源码快照------你改的代码根本进不了容器,自然不热更新。
| 启动方式 | watch 会话 | 文件同步 | 热更新 |
|---|---|---|---|
docker compose up --watch |
✅ 有 | ✅ sync | ✅ |
| Docker Desktop 点 Start | ❌ 没有 | ❌ 无同步 | ❌ 跑的是镜像快照 |
docker compose up -d(普通后台) |
❌ 没有 | ❌ 无同步 | ❌ 跑的是镜像快照 |
7.5 为什么不需要 node_modules 命名卷
旧方案(bind mount)必须挂一个 node_modules 命名卷,因为 .:/app 会把宿主机 Windows 的 node_modules 一起挂进来------里面的 .exe 二进制在 Linux 容器里跑不了,得用命名卷把容器里的 Linux 版"盖回来"。
新方案源码是 COPY 进镜像、没有 bind mount,宿主机不会污染容器内的 node_modules,sync 也只同步 ./src,所以命名卷不需要了(还能省几百 MB 磁盘)。
7.6 终极推荐:前端本地跑 + Docker 管基础设施
如果团队不强制"前端也必须在 Docker 里",最佳体验是前端在 Windows 本地原生跑(HMR 毫秒级、无中间层),Docker 只负责 MySQL、Redis、后端:
bash
docker compose up -d mysql redis # 启动基础设施
cd yudao-ui-admin-uniapp
nvm use 20 # 切 Node 版本
pnpm install
pnpm dev:h5 # 前端本地跑,改代码即时 HMR
| 方案 | HMR 延迟 | 页面状态 | 复杂度 |
|---|---|---|---|
| 前端本地跑 + Docker 管基础设施 | <100ms | ✅ 保留 | 低(推荐) |
| Docker 跑前端(COPY + sync) | 毫秒级 | ✅ 保留 | 中(本文方案) |
| Docker 跑前端(bind mount) | ❌ 不生效 | - | - |
八、常见问题排查
Q1:pnpm install 时报 MODULE_NOT_FOUND: create-base-files.js
原因 :pnpm install 自动跑 prepare 脚本,但构建时 scripts/ 目录还没拷进镜像。
解决 :加 --ignore-scripts 跳过 prepare 钩子(Dockerfile.dev 里已加)。base 文件会在运行时由 predev 钩子生成(那时源码已进镜像)。
Q2:构建时报 Corepack 下载 pnpm 失败 / 拉镜像超时
原因 :国内网络访问 registry.npmjs.org / Docker Hub 不稳定。
解决 :配镜像加速(见 3.2)。注意:改过 package.json 或 Dockerfile.dev 会触发重新构建,此时需要网络,确保加速器生效后再构建。
Q3:改代码不热更新(Windows + Docker)
先按优先级排查:
- 是不是用
up --watch启动的? 点 Start 或up -d都不会热更新(见 7.4)。 - compose 里
src是不是sync? 如果配成了sync+restart,会变成全量重启(丢状态、慢 2~5s),改成sync就是原生 HMR。 - 改的是不是
src/下的文件? 配置文件本来就要重启,不算 bug。 - 改的内容是不是"有效内容"? 测试热更新时,往
.vue文件</style>之后 append 注释是无效的------那是 SFC 语法外区域,vue 编译器会忽略,编译产物没变化,Vite 自然不会触发 HMR。要验证请在<script>里加一行真实代码。
Q4:容器里访问不到宿主机后端
解决 :确认 compose 里有 extra_hosts: - "host.docker.internal:host-gateway",且 VITE_SERVER_TARGET 指向 http://host.docker.internal:48080(后端在宿主机跑时)。
Q5:HMR 有反应,但页面刷成空白 / 状态丢了
原因 :改的文件无法热替换(比如改了 main.ts、路由配置、全局样式),Vite 自动降级为整页刷新,这是正常行为,不是故障。
Q6:Windows 上 nvm use 报 A version argument is required
Windows 用的是 nvm-windows ,它不读 .nvmrc,必须手动传版本号:
powershell
nvm use 20
# 或从 .nvmrc 读:
nvm use (Get-Content .nvmrc)
九、Docker Desktop 图形界面日常操作
9.1 查看日志
打开 Docker Desktop → Containers → 点容器 → Logs 标签页。与命令行 docker logs -f 完全等价,显示的是同一份输出。
| 操作 | 方法 |
|---|---|
| 实时跟踪 | 点右上角 ▶️ Follow log |
| 筛选关键字 | 顶部搜索框输入 |
| 清屏(不影响后台) | 🗑️ Clear |
9.2 启停容器
注意:Start/Stop 只适合"临时暂停恢复",不是日常热更新入口。 要让代码同步进容器,必须用 up --watch 启动(见 7.4)。
| 操作 | 说明 |
|---|---|
| 容器旁 ⏸️ Stop | 等价 docker stop,暂停进程,数据保留 |
| 容器旁 ▶️ Start | 等价 docker start,不带 watch,仅恢复进程 |
| 彻底删除 | docker compose down(保留镜像和数据卷) |
9.3 看磁盘占用
bash
docker system df
清理无用资源:
bash
docker container prune # 删停止的容器
docker image prune -a # 删未使用的镜像
docker volume prune # 删未引用的数据卷
十、快捷命令速查
项目 package.json 里封装了常用命令(以 uniapp 项目为例):
| 命令 | 作用 | 等价命令 |
|---|---|---|
pnpm dev:docker |
启动开发容器(带 watch) | docker compose -f docker-compose.dev.yml up --watch |
pnpm dev:docker:stop |
停止容器 | docker compose -f docker-compose.dev.yml down |
pnpm dev:docker:rebuild |
重建镜像并启动 | docker compose -f docker-compose.dev.yml up --watch --build |
pnpm dev:docker:logs |
查看日志 | docker compose -f docker-compose.dev.yml logs -f |
十一、两个项目的配置对照(vue3 / uniapp)
两个项目都已按本文方案配好,差异只在端口、环境变量和 watch 条目:
| 项 | vue3(yudao-ui-admin-vue3) | uniapp(yudao-ui-admin-uniapp) |
|---|---|---|
| 容器名 | qs-vue3-dev |
uniapp-dev |
| 端口 | 8081:80 |
9000:9000 |
| Vite 版本 | 8.0.10 | 5.2.8 |
| 后端代理变量 | VITE_PROXY_TARGET |
VITE_SERVER_TARGET |
watch 的 src |
sync |
sync |
| watch 的配置文件 | vite.config.ts / build / .env |
vite.config.ts / pages.config.ts / manifest.config.ts / uno.config.ts / env |
| 启动命令 | pnpm dev:docker |
pnpm dev:docker |
| 访问地址 | http://localhost:8081/ |
http://localhost:9000/admin-ui-uniapp/ |
两个项目结论一致:Vite 5 和 Vite 8 的 chokidar 都正常 ,
COPY + sync方案下原生 HMR 都能工作,不需要任何额外脚本。
附录:常用命令速查表
bash
# 启动(带 watch,热更新)
docker compose -f docker-compose.dev.yml up --watch
# 停止(保留容器/镜像/数据)
docker compose -f docker-compose.dev.yml stop
# 彻底删除容器(保留镜像和数据卷)
docker compose -f docker-compose.dev.yml down
# 重建镜像并启动(改了 Dockerfile 或依赖)
docker compose -f docker-compose.dev.yml up --watch --build
# 查看日志
docker compose -f docker-compose.dev.yml logs -f <服务名>
# 进入容器
docker exec -it <容器名> sh
# 查看容器状态
docker ps
# 查看磁盘占用
docker system df
# 进入容器调试
docker exec -it uniapp-dev sh
最后提醒一句 :本地开发最顺手的还是"前端本地跑 + Docker 管基础设施";如果团队要求前端也容器化,用本文第四、七章的 COPY + sync 方案即可,两条路都能拿到毫秒级 HMR 和保留页面状态。