Docker 入门第六阶段:综合实战项目

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 downup,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

设计原则:

  1. 按服务分目录:每个服务自己的 Dockerfile,互不干扰
  2. Compose 放根目录:一条命令管理整个系统
  3. 环境配置与代码分离 :密钥只在 .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 到宿主机;外部只通过 web8080 访问。

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:骨架与一键启动

  1. 建好目录与 package.json / server.js
  2. 写下生产 Compose + API Dockerfile
  3. up -d --build,调用 /health/api/hello

Day 3~4:开发体验

  1. 加入 docker-compose.dev.yml 与 bind mount
  2. 改一行 API 返回文案,确认无需重建镜像即可生效
  3. 用本机客户端连 127.0.0.1:3306

Day 5:可靠性与网络

  1. 确认 depends_on 使用 service_healthy
  2. 确认 db / redis 只在 backend 网络
  3. 做一次 downup,核对 visits 表计数未归零

Day 6:文档与交接测试

  1. 完善 README
  2. 找同学或另一台机器,只按 README 操作一遍
  3. 把卡点记入「排错」小节

验证命令清单

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.confproxy_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:从零搭起骨架

不复制粘贴整份仓库,只按文档自建:

  1. apps/apiapps/web
  2. 写最小 Dockerfile 与 Compose
  3. 浏览器点一次「调用 API」成功

练习 2:把本文替换成你自己的 Nuxt

  1. apps/web 换成 mynuxt
  2. 生产 Dockerfile:npm cinpm run build → 运行 .output 或静态导出
  3. 开发 Dockerfile:npm run dev -- --host 0.0.0.0

练习 3:压测健康检查

故意把 MySQL 密码写错,观察:

  • db healthy 是否失败
  • 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.ymldocker-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 视频」,而是:

  1. 把本阶段模板落到 真实项目 (例如 mynuxt
  2. 每次踩坑都补一行到 README
  3. 半年后回头看这份模板,你会自然萌生下一轮优化方向

附录:第六阶段速查卡

开发启动

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 从零到实战」系列的第六篇(收官篇)。建议先完成第一至第五阶段,再把本文当作实战作业手册使用。

相关推荐
Java小白笔记32 分钟前
Docker 安装配置完全指南:MacOS 、Windows、Linux环境下的安装、配置与验证
linux·macos·docker
csdn_aspnet1 小时前
GitHub Actions自动化运维实战,用CI/CD流水线实现测试、部署、安全扫描一体化
运维·安全·ci/cd·自动化·github
Inhand陈工2 小时前
数据中心UPS无功补偿与智能化监控方案:基于IG502的Modbus RTU转IEC61850实战
运维·人工智能·物联网·信息与通信
qetfw2 小时前
CentOS 7 搭建 LDAP 目录服务
linux·运维·centos
IT方大同4 小时前
linux简介
linux·运维·服务器
Dear~yxy4 小时前
时间同步服务器搭建
运维·服务器
Gem_S_6084 小时前
产品任务流转工具实践:构建需求到交付的全流程协作体系
运维
项目管理与代码4 小时前
基于Webhook+消息队列的自动化项目进度跟踪方案设计
运维·自动化
wbs_scy5 小时前
仿 muduo 高并发服务器项目:实现 LoopThread 与 LoopThreadPool 多线程事件循环
运维·服务器
微道道5 小时前
Linux盒子局域网内使用 .local 域名访问,告别IP变化烦恼
linux·运维·服务器·hermes