Lesson 0005:Docker——把你的应用装进集装箱

这是 DevOps 四大支柱的第一根:容器化。学完这节课,你能把任何项目打包成一个在任何机器上都能跑的 Docker 镜像。

为什么需要 Docker?

你肯定经历过:项目在你电脑上跑得好好的,换个环境就炸了——Node 版本不对、系统库缺失、环境变量忘配。Docker 解决这个问题的方式很简单:

把应用和它的全部依赖(OS 库、运行时、配置)打包在一起,变成一个「集装箱」。 这个集装箱在任何装了 Docker 的机器上跑,结果一模一样。

跟你熟悉的 npm 类比:package.json 锁定了 JS 依赖 → Docker 镜像锁定了一切依赖,包括操作系统级别的。

三个核心概念

概念类比是什么
Dockerfile 食谱 / package.json 一个文本文件,定义如何构建镜像。列出每一步操作。
Image(镜像) 类 (class) / 模板 Dockerfile 构建出来的只读模板。包含完整文件系统。
Container(容器) 实例 (instance) / 进程 镜像的运行实例。一个镜像可以启动多个容器。
Dockerfile ──(build)──► Image ──(run)──► Container

第一部分:写你的第一个 Dockerfile(15 分钟)

以你的 ~/code/podcast(Astro 项目)为例。这个项目的构建流程是:

npm install  →  npm run build  →  启动静态文件服务器

翻译成 Dockerfile:

# === Stage 1: Build(构建阶段)===
FROM node:22-alpine AS builder        # 以 Node.js 22 Alpine 为基础镜像
WORKDIR /app                           # 设工作目录(没有就自动创建)
COPY package*.json ./                  # 先复制依赖清单(利用缓存)
RUN npm ci                             # 安装依赖(ci = 按 lock 文件精确安装)
COPY . .                               # 复制其余源代码
RUN npm run build                      # 构建

# === Stage 2: Run(运行阶段)===
FROM node:22-alpine AS runner          # 换一个干净镜像(不要构建工具)
WORKDIR /app
COPY --from=builder /app/dist ./dist   # 只从构建阶段复制产物
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 4321                            # 声明端口(文档作用,不实际开放)
CMD ["node", "dist/server/entry.mjs"]  # 启动命令

逐行解释:

指令作用为什么这样写
FROM node:22-alpine指定基础镜像Alpine = 最小 Linux(~5MB),够用且安全
WORKDIR /app设置工作目录后续命令都在这个目录下执行
COPY pkg*.json ./复制依赖清单关键优化:只有 package.json 变了才重新 npm ci。代码变了不影响这层缓存
RUN npm ci运行命令npm cinpm install 更快更严格
COPY . .复制源代码复制,这样日常改代码时只重建这层
--from=builder多阶段构建最终镜像只含运行时需要的东西,不包含 npm、构建工具等垃圾
EXPOSE 4321声明端口文档性质的,实际端口映射在 docker run -p 时指定
CMD [...]容器启动时执行的命令JSON 数组格式(exec form),推荐用这个
为什么 COPY 要分两步?Docker 构建是分层缓存的。每一行指令生成一层。如果 COPY . . 在前面,你每次改一行代码,整层缓存都失效,后面 RUN npm ci 还得重跑。把 COPY package*.json 提前,依赖层就缓存住了——除非你改了 package.json。

第二部分:8 个核心命令(10 分钟)

打开终端,跟着敲。

# 1. 构建镜像
docker build -t my-app:latest .        # -t = 名字:标签  . = Dockerfile 所在目录

# 2. 查看本地镜像
docker images                           # 列出所有本地镜像

# 3. 运行容器
docker run -d -p 8080:4321 --name my-app my-app:latest
#          ↑  ↑           ↑              ↑
#      后台跑 端口映射    容器名         镜像名
#      宿主:容器

# 4. 查看运行中的容器
docker ps                               # 运行中的
docker ps -a                            # 包括已停止的

# 5. 查看日志
docker logs my-app                      # 一次性输出
docker logs -f my-app                   # 实时追踪(Ctrl+C 退出)

# 6. 进入容器内部
docker exec -it my-app sh               # -it = 交互式终端  sh = shell

# 7. 停止 / 启动 / 删除
docker stop my-app                      # 停止
docker start my-app                     # 重新启动
docker rm my-app                        # 删除容器(先 stop)
docker rm -f my-app                     # 强制删除(stop + rm)

# 8. 清理
docker system prune -a                  # 删除所有未使用的镜像/容器/网络
# ⚠️ 这条会清掉很多东西,确认后再跑

第三部分:Docker Compose——管理多容器应用

真实项目很少只有一个容器。比如你的应用可能需要 Node.js + PostgreSQL + Redis。手动 docker run 三个容器再连网络太痛苦了。Docker Compose 用一个 YAML 文件定义所有服务。

# docker-compose.yml
services:
  app:
    build: .                       # 用当前目录的 Dockerfile 构建
    ports:
      - "4321:4321"
    environment:
      - DATABASE_URL=postgres://db:5432/myapp    # db = 下面的服务名,自动 DNS
    depends_on:
      - db

  db:
    image: postgres:16-alpine      # 直接用官方镜像,不需要自己写 Dockerfile
    environment:
      - POSTGRES_PASSWORD=secret
    volumes:
      - pgdata:/var/lib/postgresql/data   # 持久化数据(否则容器删了数据就没了)

volumes:
  pgdata:                          # 声明一个命名卷

启动:docker compose up -d。停止:docker compose down

动手练习:Docker 化一个 Node.js 应用

任务:创建一个最小的 Node.js HTTP 服务,写 Dockerfile,构建并运行。

# 1. 创建项目
mkdir /tmp/docker-demo && cd /tmp/docker-demo

# 2. 创建 server.js
cat > server.js << 'EOF'
const http = require('http');
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello from Docker!\n');
});
server.listen(3000, () => console.log('Listening on :3000'));
EOF

# 3. 创建 package.json(极简版)
echo '{"name":"docker-demo","private":true}' > package.json

# 4. 创建 Dockerfile
cat > Dockerfile << 'EOF'
FROM node:22-alpine
WORKDIR /app
COPY package.json ./
COPY server.js ./
EXPOSE 3000
CMD ["node", "server.js"]
EOF

# 5. 构建并运行
docker build -t docker-demo .
docker run -d -p 3000:3000 --name demo docker-demo

# 6. 验证
curl http://localhost:3000
# 输出: Hello from Docker!

# 7. 进容器看看
docker exec -it demo sh
  # 现在你在容器里!ls 看看,只有 package.json 和 server.js
  # exit 退出

# 8. 清理
docker rm -f demo
如果 3000 端口被占用了,换一个:-p 3456:3000,然后 curl http://localhost:3456

小测验

Q1:Dockerfile → Image → Container 的关系是什么?
Q2:为什么要把 COPY package*.json 放在 COPY . . 前面?
Q3:要进入一个运行中容器的 Shell,用什么命令?
Q4:多阶段构建(multi-stage build)的主要目的是什么?

推荐资源

下一步

下一课我们学习 CI/CD 基础——GitHub Actions。你将学会:每次 push 代码,自动构建 Docker 镜像、跑测试、部署。这是 DevOps 最核心的自动化实践。

构建失败?把报错贴给我。最常见的坑是端口冲突和 COPY 路径写错。记住 docker logs 容器名 是你的第一排查工具。
← Lesson 0004 · 术语表 · Mission