
Docker 入门第六阶段综合实战项目系列Docker 从零到实战阶段第六阶段综合实战预计学习时间12 周前置要求完成第一至第五阶段更新时间2026 年 7 月写在前面前五个阶段你已经学完了 Docker 的核心能力概念、命令、Dockerfile、Compose、网络与生产实践。第六阶段不再学新概念而是把它们串成一套可交付的成果。本阶段的目标完成一个「新人git clone后 10 分钟内可跑起来」的容器化项目模板。学完本文你将能够为自己的 Nuxt / Node 全栈项目写出生产级 Docker 配置拆分开发环境与生产环境的 Compose 文件写出清爽的 README让同事一条命令启动开发环境用检查清单验收开发热更新、数据持久化、健康检查、网络隔离把这套模板复用到以后的项目本文以「Nuxt 前端 Node API MySQL Redis」为例。若你只有 Nuxt 或只有 API删减对应服务即可。目录1. 本阶段学习地图2. 实战目标与验收标准3. 项目结构设计4. 应用代码最小可运行5. Dockerfile开发与生产双轨6. Compose开发与生产分离7. 环境变量与文档8. 按步骤落地与验证9. 排错手册10. 动手练习11. 阶段小结与自测12. 系列收官与进阶方向1. 本阶段学习地图第六阶段综合实战 │ ├── 目标定义 │ ├── 验收标准 │ └── 架构选型 │ ├── 项目骨架 │ ├── apps/webNuxt 或静态前端 │ ├── apps/apiNode Express │ ├── docker/Dockerfile 与 Nginx 配置 │ └── compose 文件dev / prod │ ├── 配置落地 │ ├── 多阶段构建 │ ├── healthcheck 网络隔离 │ └── .env / README │ └── 验收交付 ├── 开发一键启动 热更新 ├── 生产镜像体积 持久化 └── 文档新人 10 分钟上手2. 实战目标与验收标准2.1 交付物清单交付物说明docker-compose.yml生产基础配置docker-compose.dev.yml开发环境覆盖Dockerfile/Dockerfile.dev生产与开发镜像构建.env.example配置模板README.md中的 Docker 章节启动、排错、目录说明一次成功的演示up→ 浏览器可访问 → 数据不丢2.2 验收标准全部勾选才算完成docker compose ... up -d --build后前端和 API 可访问修改 API 代码后开发环境无需重建镜像即可生效热更新docker compose down再upMySQL 数据仍在API 在数据库健康后才完成业务初始化healthcheck数据库与 Redis 未暴露到公网生产配置不下沉到0.0.0.0全网监听或仅127.0.0.1.env未提交到 Git且存在.env.example新人按 README 操作不超过 10 分钟能跑通2.3 推荐架构浏览器 │ ▼ ┌──────┐ frontend 网络 ┌──────┐ │ web │ ───────────────────→ │ api │ └──────┘ └──┬───┘ │ backend 网络 ┌──────┴──────┐ ▼ ▼ ┌────┐ ┌─────┐ │ db │ │redis│ └────┘ └─────┘3. 项目结构设计建议最终目录如下名称可按自己项目调整my-docker-project/ ├── apps/ │ ├── api/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── server.js │ │ ├── Dockerfile │ │ └── Dockerfile.dev │ └── web/ │ ├── package.json # 若用 Nuxt │ ├── nuxt.config.ts │ ├── Dockerfile │ ├── Dockerfile.dev │ └── nginx.conf # 生产静态托管时使用 ├── docker-compose.yml ├── docker-compose.dev.yml ├── .env.example ├── .gitignore └── README.md设计原则按服务分目录每个服务自己的 Dockerfile互不干扰Compose 放根目录一条命令管理整个系统环境配置与代码分离密钥只在.env若你目前只有单个 Nuxt 项目可先做「web nginx 可选mock api」简化版以后再加数据库。4. 应用代码最小可运行以下示例刻意保持最小方便你替换成自己的mynuxt业务代码。4.1 APIapps/api/package.json{name:api,version:1.0.0,type:module,scripts:{start:node src/server.js,dev:node --watch src/server.js},dependencies:{express:^4.19.0,mysql2:^3.11.0,redis:^4.7.0}}4.2 APIapps/api/src/server.jsimportexpressfromexpress;importmysqlfrommysql2/promise;import{createClient}fromredis;constappexpress();constPORTprocess.env.PORT||3000;asyncfunctionwaitReady(fn,name,retries20){for(leti0;iretries;i){try{returnawaitfn();}catch(err){console.log([wait]${name}not ready (${i1}/${retries}):${err.message});awaitnewPromise((r)setTimeout(r,2000));}}thrownewError(${name}not ready);}asyncfunctionmain(){constdbawaitwaitReady(()mysql.createConnection({host:process.env.DB_HOST||db,user:process.env.DB_USER||root,password:process.env.DB_PASSWORD,database:process.env.DB_NAME||app_db,}),mysql);constrediscreateClient({url:process.env.REDIS_URL||redis://redis:6379,});awaitwaitReady(()redis.connect(),redis);awaitdb.query(CREATE TABLE IF NOT EXISTS visits ( id INT AUTO_INCREMENT PRIMARY KEY, visited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ));app.get(/health,(_req,res)res.json({status:ok}));app.get(/api/hello,async(_req,res){awaitdb.query(INSERT INTO visits () VALUES ());const[rows]awaitdb.query(SELECT COUNT(*) AS total FROM visits);consthitsawaitredis.incr(api_hits);res.json({message:Hello from Dockerized API,visits:rows[0].total,redisHits:hits,});});app.listen(PORT,()console.log(API listening on${PORT}));}main().catch((err){console.error(err);process.exit(1);});4.3 Web两种实现路径二选一路径 A已有 Nuxt 项目把现有mynuxt放到apps/web生产 Dockerfile 做多阶段npm run build开发用npm run dev。路径 B用 Nginx 简单 HTML 先跑通本章示例apps/web/index.html!doctypehtmlhtmllangzh-CNheadmetacharsetUTF-8/titleDocker 第六阶段演示/title/headbodyh1Docker 综合实战/h1buttonidbtn调用 API/buttonpreidout等待响应.../prescriptdocument.getElementById(btn).onclickasync(){constresawaitfetch(/api/hello);constdataawaitres.json();document.getElementById(out).textContentJSON.stringify(data,null,2);};/script/body/htmlapps/web/nginx.confserver { listen 80; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://api:3000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }关键点浏览器访问/api/...由 Nginx 反代到 Compose 网络里的api:3000前端不必写死后端 IP。5. Dockerfile开发与生产双轨5.1 API 生产apps/api/DockerfileFROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --omitdev FROM node:20-alpine WORKDIR /app ENV NODE_ENVproduction COPY --fromdeps /app/node_modules ./node_modules COPY package*.json ./ COPY src ./src USER node EXPOSE 3000 CMD [npm, start] HEALTHCHECK --interval15s --timeout5s --retries3 --start-period10s \ CMD wget -qO- http://127.0.0.1:3000/health || exit 15.2 API 开发apps/api/Dockerfile.devFROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD [npm, run, dev]5.3 Web 生产apps/web/DockerfileFROM nginx:1.25-alpine COPY index.html /usr/share/nginx/html/index.html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]若是 Nuxt SSR生产多为node .output/server/index.mjs若是静态站点多阶段npm run generate再拷到 Nginx。5.4 Web 开发Nuxt 示例可选FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD [npm, run, dev, --, --host, 0.0.0.0]6. Compose开发与生产分离6.1 生产基础docker-compose.ymlservices:web:build:context:./apps/webdockerfile:Dockerfileports:-${WEB_PORT:-8080}:80depends_on:api:condition:service_healthynetworks:-frontendrestart:unless-stoppedlogging:driver:json-fileoptions:max-size:10mmax-file:3api:build:context:./apps/apidockerfile:Dockerfileenvironment:NODE_ENV:productionPORT:3000DB_HOST:dbDB_USER:${MYSQL_USER}DB_PASSWORD:${MYSQL_PASSWORD}DB_NAME:${MYSQL_DATABASE}REDIS_URL:redis://redis:6379depends_on:db:condition:service_healthyredis:condition:service_healthynetworks:-frontend-backendrestart:unless-stoppedhealthcheck:test:[CMD,wget,-qO-,http://127.0.0.1:3000/health]interval:15stimeout:5sretries:3start_period:15sdb:image:mysql:8.0environment:MYSQL_ROOT_PASSWORD:${MYSQL_ROOT_PASSWORD}MYSQL_DATABASE:${MYSQL_DATABASE}MYSQL_USER:${MYSQL_USER}MYSQL_PASSWORD:${MYSQL_PASSWORD}volumes:-db_data:/var/lib/mysqlnetworks:-backendrestart:unless-stoppedhealthcheck:test:[CMD,mysqladmin,ping,-h,localhost]interval:10sretries:5start_period:30sredis:image:redis:7-alpinevolumes:-redis_data:/datanetworks:-backendrestart:unless-stoppedhealthcheck:test:[CMD,redis-cli,ping]interval:10sretries:3networks:frontend:backend:volumes:db_data:redis_data:注意生产不映射db/redis到宿主机外部只通过web的8080访问。6.2 开发覆盖docker-compose.dev.ymlservices:api:build:context:./apps/apidockerfile:Dockerfile.devvolumes:-./apps/api:/app-/app/node_modulescommand:npm run devports:-3000:3000environment:NODE_ENV:developmentweb:# 静态示例开发也可直接用生产 Nginx 镜像或改为 Nuxt dev 容器volumes:-./apps/web/index.html:/usr/share/nginx/html/index.html:ro-./apps/web/nginx.conf:/etc/nginx/conf.d/default.conf:roports:-${WEB_PORT:-8080}:80db:ports:-127.0.0.1:3306:3306redis:ports:-127.0.0.1:6379:6379开发环境说明API 用 bind mount --watch改代码即生效数据库、Redis 只绑定本机127.0.0.1方便 Navicat / RedisInsight不要把开发端口映射习惯带到生产配置6.3 启动命令# 开发dockercompose-fdocker-compose.yml-fdocker-compose.dev.yml up-d--build# 生产dockercompose --env-file .env.prod up-d--build# 停止保留数据dockercompose-fdocker-compose.yml-fdocker-compose.dev.yml down# 停止并清空数据卷慎用dockercompose-fdocker-compose.yml-fdocker-compose.dev.yml down-v7. 环境变量与文档7.1.env.example# Web WEB_PORT8080 # MySQL MYSQL_ROOT_PASSWORDchange_me_root MYSQL_DATABASEapp_db MYSQL_USERapp_user MYSQL_PASSWORDchange_me_user # 可选生产单独复制为 .env.prod7.2.gitignore必加.env .env.prod .env.local node_modules/7.3 README 最小 Docker 章节建议原文放入项目## 使用 Docker 运行本项目 ### 前置条件 - 已安装 Docker DesktopWindows / macOS或 Docker Engine ComposeLinux - 已配置国内镜像加速可选国内网络推荐 ### 快速开始开发 bash cp .env.example .env # 编辑 .env填入密码 docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build 访问 - 前端http://localhost:8080 - APIhttp://localhost:3000/health开发环境直连 ### 常用命令 bash docker compose -f docker-compose.yml -f docker-compose.dev.yml ps docker compose -f docker-compose.yml -f docker-compose.dev.yml logs -f api docker compose -f docker-compose.yml -f docker-compose.dev.yml exec api sh ### 排错 1. Cannot connect to the Docker daemon → 启动 Docker Desktop 2. 连不上数据库 → docker compose ... ps 看 db 是否 healthy等 30 秒 3. 端口占用 → 修改 .env 里的 WEB_PORT8. 按步骤落地与验证Day 12骨架与一键启动建好目录与package.json/server.js写下生产 Compose API Dockerfileup -d --build调用/health和/api/helloDay 34开发体验加入docker-compose.dev.yml与 bind mount改一行 API 返回文案确认无需重建镜像即可生效用本机客户端连127.0.0.1:3306Day 5可靠性与网络确认depends_on使用service_healthy确认db/redis只在backend网络做一次down→up核对 visits 表计数未归零Day 6文档与交接测试完善 README找同学或另一台机器只按 README 操作一遍把卡点记入「排错」小节验证命令清单# 1. 服务状态dockercompose-fdocker-compose.yml-fdocker-compose.dev.ymlps# 2. 健康与业务接口curlhttp://localhost:3000/healthcurlhttp://localhost:8080/api/hello# 3. 数据持久化dockercompose-fdocker-compose.yml-fdocker-compose.dev.yml downdockercompose-fdocker-compose.yml-fdocker-compose.dev.yml up-dcurlhttp://localhost:8080/api/hello# visits 应继续累加而不是从 1 重新开始# 4. 数据库连通开发端口dockercompose-fdocker-compose.yml-fdocker-compose.dev.ymlexecdb\mysql -u${MYSQL_USER}-p${MYSQL_PASSWORD}-eSELECT COUNT(*) FROM app_db.visits;9. 排错手册现象可能原因处理api一直重启数据库未就绪 / 密码错误看logs api核对.env与 Compose 变量前端按钮报 Network ErrorNginx 反代路径不对检查nginx.conf的proxy_pass是否指向api:3000改了代码不生效开发未挂载卷 / 未用Dockerfile.dev确认compose.dev.yml与启动命令带了两个-fnode_modules二进制异常Windows bind mount 覆盖 Linux 依赖加匿名卷/app/node_modulesNuxt 容器里访问不到 HMR未--host 0.0.0.0dev命令加 host 参数并映射端口镜像过大未多阶段 / 未.dockerignore参考第五阶段优化清单wget: not foundhealthcheckalpine 精简镜像缺工具在 Dockerfile 安装wget或改用node -e/curlalpine上健康检查另一种写法healthcheck:test:[CMD,node,-e,fetch(http://127.0.0.1:3000/health).then(rprocess.exit(r.ok?0:1)).catch(()process.exit(1))]10. 动手练习练习 1从零搭起骨架不复制粘贴整份仓库只按文档自建建apps/api、apps/web写最小 Dockerfile 与 Compose浏览器点一次「调用 API」成功练习 2把本文替换成你自己的 Nuxtapps/web换成mynuxt生产 Dockerfilenpm ci→npm run build→ 运行.output或静态导出开发 Dockerfilenpm run dev -- --host 0.0.0.0练习 3压测健康检查故意把 MySQL 密码写错观察dbhealthy 是否失败api是否不启动或持续重试修正密码后up是否自愈练习 4写出团队级 README 验收把电脑交给同事只允许看 README超过 15 分钟仍跑不通 → README 不够完整10 分钟内跑通 → 本阶段目标达成11. 阶段小结与自测11.1 你应该带走的成果可复用 Docker 项目模板 ├── 双轨 Dockerfiledev / prod ├── 双轨 Composedev / prod ├── 网络隔离 healthcheck ├── 命名卷持久化 ├── .env.example README └── 自己项目的一次真实容器化记录11.2 自测清单能解释为什么生产不把 MySQL 映射到公网能一句话说明docker-compose.yml与docker-compose.dev.yml的分工能证明「重启后数据仍在」能证明「开发改代码免重建」能把模板迁移到第二个项目改动不超过半天11.3 第一六阶段能力对照阶段能力本实战对应点一概念与第一次 run理解每个服务为何是容器二日常命令ps/logs/exec排错三DockerfileAPI / Web 镜像构建四Compose多服务一键启停五网络 · 存储 · 生产healthcheck、网络、volume、日志限制六综合交付模板 文档 验收12. 系列收官与进阶方向到第六阶段结束「Docker 从零到实战」基础路线已经走完。你已经足够应对个人项目容器化团队开发环境统一简单生产部署单机 Compose接下来按需学习不必一口气全学方向何时需要学什么CI/CD每次提交要自动构建镜像GitHub Actions / GitLab CI 构建推送镜像私有仓库团队要共享镜像Harbor / 阿里云 ACR / Docker Hub 私有库镜像安全上线合规Trivy 扫描、非 root 用户、最小基础镜像编排升级多机 / 自动扩缩KubernetesK8s入门可观测性故障定位统一日志、Prometheus Grafana进阶给你的结束建议不要追求「再看十个 Docker 视频」而是把本阶段模板落到真实项目例如mynuxt每次踩坑都补一行到 README半年后回头看这份模板你会自然萌生下一轮优化方向附录第六阶段速查卡开发启动cp.env.example .envdockercompose-fdocker-compose.yml-fdocker-compose.dev.yml up-d--buildcurlhttp://localhost:8080/api/hello生产启动cp.env.example .env.prod# 修改生产密码与端口dockercompose --env-file .env.prod up-d--build日常运维dockercompose-fdocker-compose.yml-fdocker-compose.dev.ymlpsdockercompose-fdocker-compose.yml-fdocker-compose.dev.yml logs-fapidockercompose-fdocker-compose.yml-fdocker-compose.dev.ymlexecdb mysql-uapp_user-pdockercompose-fdocker-compose.yml-fdocker-compose.dev.yml down毕业验收一句话我有一份 Compose 模板别人能用它 10 分钟跑起我的项目我自己能用它把开发环境和生产部署分开管理。学习建议第六阶段没有捷径——只有把配置写进真实项目才算完成。若你完成了「mynuxt API MySQL」任一组合并写出了 README这个系列的目标就达成了。系列到此收官。后面遇到部署、CI 或 K8s可以按上面的进阶表按需打开。本文是「Docker 从零到实战」系列的第六篇收官篇。建议先完成第一至第五阶段再把本文当作实战作业手册使用。