Docker 入门第六阶段:综合实战项目
系列:Docker 从零到实战
阶段:第六阶段(综合实战)
预计学习时间:1~2 周
前置要求:完成第一至第五阶段
更新时间:2026 年 7 月
写在前面
前五个阶段你已经学完了 Docker 的核心能力:概念、命令、Dockerfile、Compose、网络与生产实践。第六阶段不再学新概念,而是把它们 串成一套可交付的成果。
本阶段的目标:
完成一个「新人
git clone后 10 分钟内可跑起来」的容器化项目模板。
学完本文,你将能够:
- 为自己的 Nuxt / Node 全栈项目写出生产级 Docker 配置
- 拆分开发环境与生产环境的 Compose 文件
- 写出清爽的 README,让同事一条命令启动开发环境
- 用检查清单验收:开发热更新、数据持久化、健康检查、网络隔离
- 把这套模板复用到以后的项目
本文以 「Nuxt 前端 + Node API + MySQL + Redis」 为例。若你只有 Nuxt 或只有 API,删减对应服务即可。
目录
- [1. 本阶段学习地图](#1. 本阶段学习地图)
- [2. 实战目标与验收标准](#2. 实战目标与验收标准)
- [3. 项目结构设计](#3. 项目结构设计)
- [4. 应用代码(最小可运行)](#4. 应用代码(最小可运行))
- [5. Dockerfile:开发与生产双轨](#5. Dockerfile:开发与生产双轨)
- [6. Compose:开发与生产分离](#6. Compose:开发与生产分离)
- [7. 环境变量与文档](#7. 环境变量与文档)
- [8. 按步骤落地与验证](#8. 按步骤落地与验证)
- [9. 排错手册](#9. 排错手册)
- [10. 动手练习](#10. 动手练习)
- [11. 阶段小结与自测](#11. 阶段小结与自测)
- [12. 系列收官与进阶方向](#12. 系列收官与进阶方向)
1. 本阶段学习地图
第六阶段:综合实战
│
├── 目标定义
│ ├── 验收标准
│ └── 架构选型
│
├── 项目骨架
│ ├── apps/web(Nuxt 或静态前端)
│ ├── apps/api(Node 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再up,MySQL 数据仍在 - 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 API:apps/api/package.json
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 API:apps/api/src/server.js
javascript
import express from 'express';
import mysql from 'mysql2/promise';
import { createClient } from 'redis';
const app = express();
const PORT = process.env.PORT || 3000;
async function waitReady(fn, name, retries = 20) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (err) {
console.log(`[wait] ${name} not ready (${i + 1}/${retries}): ${err.message}`);
await new Promise((r) => setTimeout(r, 2000));
}
}
throw new Error(`${name} not ready`);
}
async function main() {
const db = await waitReady(
() =>
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'
);
const redis = createClient({
url: process.env.REDIS_URL || 'redis://redis:6379',
});
await waitReady(() => redis.connect(), 'redis');
await db.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) => {
await db.query('INSERT INTO visits () VALUES ()');
const [rows] = await db.query('SELECT COUNT(*) AS total FROM visits');
const hits = await redis.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:
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Docker 第六阶段演示</title>
</head>
<body>
<h1>Docker 综合实战</h1>
<button id="btn">调用 API</button>
<pre id="out">等待响应...</pre>
<script>
document.getElementById('btn').onclick = async () => {
const res = await fetch('/api/hello');
const data = await res.json();
document.getElementById('out').textContent = JSON.stringify(data, null, 2);
};
</script>
</body>
</html>
apps/web/nginx.conf:
nginx
server {
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/Dockerfile
dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY package*.json ./
COPY src ./src
USER node
EXPOSE 3000
CMD ["npm", "start"]
HEALTHCHECK --interval=15s --timeout=5s --retries=3 --start-period=10s \
CMD wget -qO- http://127.0.0.1:3000/health || exit 1
5.2 API 开发:apps/api/Dockerfile.dev
dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
5.3 Web 生产:apps/web/Dockerfile
dockerfile
FROM 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 示例,可选)
dockerfile
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.yml
yaml
services:
web:
build:
context: ./apps/web
dockerfile: Dockerfile
ports:
- "${WEB_PORT:-8080}:80"
depends_on:
api:
condition: service_healthy
networks:
- frontend
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
api:
build:
context: ./apps/api
dockerfile: Dockerfile
environment:
NODE_ENV: production
PORT: 3000
DB_HOST: db
DB_USER: ${MYSQL_USER}
DB_PASSWORD: ${MYSQL_PASSWORD}
DB_NAME: ${MYSQL_DATABASE}
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 15s
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
start_period: 30s
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
retries: 3
networks:
frontend:
backend:
volumes:
db_data:
redis_data:
注意:生产 不映射 db / redis 到宿主机;外部只通过 web 的 8080 访问。
6.2 开发覆盖:docker-compose.dev.yml
yaml
services:
api:
build:
context: ./apps/api
dockerfile: Dockerfile.dev
volumes:
- ./apps/api:/app
- /app/node_modules
command: npm run dev
ports:
- "3000:3000"
environment:
NODE_ENV: development
web:
# 静态示例:开发也可直接用生产 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:ro
ports:
- "${WEB_PORT:-8080}:80"
db:
ports:
- "127.0.0.1:3306:3306"
redis:
ports:
- "127.0.0.1:6379:6379"
开发环境说明:
- API 用 bind mount +
--watch,改代码即生效 - 数据库、Redis 只绑定本机
127.0.0.1,方便 Navicat / RedisInsight - 不要把开发端口映射习惯带到生产配置
6.3 启动命令
bash
# 开发
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
# 生产
docker compose --env-file .env.prod up -d --build
# 停止(保留数据)
docker compose -f docker-compose.yml -f docker-compose.dev.yml down
# 停止并清空数据卷(慎用)
docker compose -f docker-compose.yml -f docker-compose.dev.yml down -v
7. 环境变量与文档
7.1 .env.example
env
# Web
WEB_PORT=8080
# MySQL
MYSQL_ROOT_PASSWORD=change_me_root
MYSQL_DATABASE=app_db
MYSQL_USER=app_user
MYSQL_PASSWORD=change_me_user
# 可选:生产单独复制为 .env.prod
7.2 .gitignore 必加
gitignore
.env
.env.prod
.env.local
node_modules/
7.3 README 最小 Docker 章节(建议原文放入项目)
markdown
## 使用 Docker 运行本项目
### 前置条件
- 已安装 Docker Desktop(Windows / macOS)或 Docker Engine + Compose(Linux)
- 已配置国内镜像加速(可选,国内网络推荐)
### 快速开始(开发)
```bash
cp .env.example .env
# 编辑 .env,填入密码
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
```
访问:
- 前端:http://localhost:8080
- API:http://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_PORT`
8. 按步骤落地与验证
Day 1~2:骨架与一键启动
- 建好目录与
package.json/server.js - 写下生产 Compose + API Dockerfile
up -d --build,调用/health和/api/hello
Day 3~4:开发体验
- 加入
docker-compose.dev.yml与 bind mount - 改一行 API 返回文案,确认无需重建镜像即可生效
- 用本机客户端连
127.0.0.1:3306
Day 5:可靠性与网络
- 确认
depends_on使用service_healthy - 确认
db/redis只在backend网络 - 做一次
down→up,核对 visits 表计数未归零
Day 6:文档与交接测试
- 完善 README
- 找同学或另一台机器,只按 README 操作一遍
- 把卡点记入「排错」小节
验证命令清单
bash
# 1. 服务状态
docker compose -f docker-compose.yml -f docker-compose.dev.yml ps
# 2. 健康与业务接口
curl http://localhost:3000/health
curl http://localhost:8080/api/hello
# 3. 数据持久化
docker compose -f docker-compose.yml -f docker-compose.dev.yml down
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
curl http://localhost:8080/api/hello
# visits 应继续累加,而不是从 1 重新开始
# 4. 数据库连通(开发端口)
docker compose -f docker-compose.yml -f docker-compose.dev.yml exec db \
mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} -e "SELECT COUNT(*) FROM app_db.visits;"
9. 排错手册
| 现象 | 可能原因 | 处理 |
|---|---|---|
api 一直重启 |
数据库未就绪 / 密码错误 | 看 logs api,核对 .env 与 Compose 变量 |
| 前端按钮报 Network Error | Nginx 反代路径不对 | 检查 nginx.conf 的 proxy_pass 是否指向 api:3000 |
| 改了代码不生效(开发) | 未挂载卷 / 未用 Dockerfile.dev |
确认 compose.dev.yml 与启动命令带了两个 -f |
node_modules 二进制异常 |
Windows bind mount 覆盖 Linux 依赖 | 加匿名卷 /app/node_modules |
| Nuxt 容器里访问不到 HMR | 未 --host 0.0.0.0 |
dev 命令加 host 参数,并映射端口 |
| 镜像过大 | 未多阶段 / 未 .dockerignore |
参考第五阶段优化清单 |
wget: not found(healthcheck) |
alpine 精简镜像缺工具 | 在 Dockerfile 安装 wget,或改用 node -e / curl |
alpine 上健康检查另一种写法:
yaml
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
10. 动手练习
练习 1:从零搭起骨架
不复制粘贴整份仓库,只按文档自建:
- 建
apps/api、apps/web - 写最小 Dockerfile 与 Compose
- 浏览器点一次「调用 API」成功
练习 2:把本文替换成你自己的 Nuxt
apps/web换成mynuxt- 生产 Dockerfile:
npm ci→npm run build→ 运行.output或静态导出 - 开发 Dockerfile:
npm run dev -- --host 0.0.0.0
练习 3:压测健康检查
故意把 MySQL 密码写错,观察:
dbhealthy 是否失败api是否不启动或持续重试- 修正密码后
up是否自愈
练习 4:写出团队级 README 验收
把电脑交给同事,只允许看 README:
- 超过 15 分钟仍跑不通 → README 不够完整
- 10 分钟内跑通 → 本阶段目标达成
11. 阶段小结与自测
11.1 你应该带走的成果
可复用 Docker 项目模板
├── 双轨 Dockerfile(dev / prod)
├── 双轨 Compose(dev / prod)
├── 网络隔离 + healthcheck
├── 命名卷持久化
├── .env.example + README
└── 自己项目的一次真实容器化记录
11.2 自测清单
- 能解释为什么生产不把 MySQL 映射到公网?
- 能一句话说明
docker-compose.yml与docker-compose.dev.yml的分工? - 能证明「重启后数据仍在」?
- 能证明「开发改代码免重建」?
- 能把模板迁移到第二个项目,改动不超过半天?
11.3 第一~六阶段能力对照
| 阶段 | 能力 | 本实战对应点 |
|---|---|---|
| 一 | 概念与第一次 run | 理解每个服务为何是容器 |
| 二 | 日常命令 | ps / logs / exec 排错 |
| 三 | Dockerfile | API / Web 镜像构建 |
| 四 | Compose | 多服务一键启停 |
| 五 | 网络 · 存储 · 生产 | healthcheck、网络、volume、日志限制 |
| 六 | 综合交付 | 模板 + 文档 + 验收 |
12. 系列收官与进阶方向
到第六阶段结束,「Docker 从零到实战」基础路线已经走完。你已经足够应对:
- 个人项目容器化
- 团队开发环境统一
- 简单生产部署(单机 Compose)
接下来按需学习(不必一口气全学)
| 方向 | 何时需要 | 学什么 |
|---|---|---|
| CI/CD | 每次提交要自动构建镜像 | GitHub Actions / GitLab CI 构建推送镜像 |
| 私有仓库 | 团队要共享镜像 | Harbor / 阿里云 ACR / Docker Hub 私有库 |
| 镜像安全 | 上线合规 | Trivy 扫描、非 root 用户、最小基础镜像 |
| 编排升级 | 多机 / 自动扩缩 | Kubernetes(K8s)入门 |
| 可观测性 | 故障定位 | 统一日志、Prometheus + Grafana(进阶) |
给你的结束建议
不要追求「再看十个 Docker 视频」,而是:
- 把本阶段模板落到 真实项目 (例如
mynuxt) - 每次踩坑都补一行到 README
- 半年后回头看这份模板,你会自然萌生下一轮优化方向
附录:第六阶段速查卡
开发启动
bash
cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
curl http://localhost:8080/api/hello
生产启动
bash
cp .env.example .env.prod
# 修改生产密码与端口
docker compose --env-file .env.prod up -d --build
日常运维
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 db mysql -uapp_user -p
docker compose -f docker-compose.yml -f docker-compose.dev.yml down
毕业验收一句话
我有一份 Compose 模板:别人能用它 10 分钟跑起我的项目,我自己能用它把开发环境和生产部署分开管理。
学习建议: 第六阶段没有捷径------只有把配置写进真实项目才算完成。若你完成了「mynuxt + API + MySQL」任一组合,并写出了 README,这个系列的目标就达成了。
系列到此收官。后面遇到部署、CI 或 K8s,可以按上面的进阶表按需打开。
本文是「Docker 从零到实战」系列的第六篇(收官篇)。建议先完成第一至第五阶段,再把本文当作实战作业手册使用。