Skip to content

Docker 系统学习文档 ​

从零开始掌握 Docker,涵盖 Docker Desktop 和 Linux Docker

从基础概念到项目开发、部署、CI/CD 全流程


目录 ​

第一篇:Docker 基础概念 ​

  1. 什么是 Docker
  2. 容器 vs 虚拟机
  3. Docker 三大核心概念
  4. Docker 架构

第二篇:安装与环境搭建 ​

  1. Docker Desktop(Windows/Mac)
  2. Linux Docker(Ubuntu/CentOS)
  3. WSL2 集成(Windows 最佳实践)
  4. 验证安装

第三篇:Docker 基础操作 ​

  1. 镜像管理
  2. 容器管理
  3. 数据卷(Volume)
  4. 网络管理

第四篇:Dockerfile 编写指南 ​

  1. Dockerfile 基础指令
  2. 多阶段构建
  3. 最佳实践

第五篇:Docker Compose ​

  1. Compose 文件结构
  2. 常用配置示例
  3. Compose 实战:全栈项目

第六篇:项目开发到部署全流程 ​

  1. 本地开发环境搭建
  2. 生产环境镜像构建
  3. 镜像仓库与分发
  4. 服务器部署

第七篇:CI/CD 集成 ​

  1. GitHub Actions + Docker
  2. GitLab CI + Docker
  3. 自动化部署流水线

第八篇:进阶话题 ​

  1. Docker 安全最佳实践
  2. 容器编排(Kubernetes 简介)
  3. 监控与日志
  4. 性能调优


第一篇:Docker 基础概念 ​


1.1 什么是 Docker ​

Docker 是一个开源的容器化平台,让你可以将应用程序及其依赖打包到一个轻量级、可移植的容器中,然后在任何 Linux 或 Windows 机器上运行。

核心标语:"Build once, run anywhere"(一次构建,到处运行)

能解决什么问题?

在没有 Docker 之前,开发环境经常面临这些问题:

markdown
问题 1:"在我机器上能跑啊"
  → 开发者的系统、依赖版本、配置和生产环境不同
  → 代码部署后各种报错

问题 2:环境配置繁琐
  → 新同事加入,花 2 天配置开发环境
  → 安装 Node.js、MySQL、Redis、Nginx……
  → 版本冲突、系统兼容性问题

问题 3:多项目隔离
  → 项目 A 需要 Node 20,项目 B 需要 Node 22
  → 安装、卸载、切换版本……

Docker 的解决方案:

yaml
# 用 Docker 描述项目环境,一行命令启动
docker compose up
# Docker 会自动:
# 1. 拉取 Node 22 镜像
# 2. 拉取 MySQL 8.0 镜像
# 3. 创建网络让它们通信
# 4. 挂载代码目录
# 5. 启动项目

# 新同事加入:只需装 Docker → git clone → docker compose up

1.2 容器 vs 虚拟机 ​

这是 Docker 初学者最需要理解的核心概念。

┌─────────────────────────────────────┐
│              虚拟机架构              │
├─────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐         │
│  │  应用 A  │  │  应用 B  │         │
│  ├──────────┤  ├──────────┤         │
│  │ Bins/Lib │  │ Bins/Lib │         │
│  ├──────────┤  ├──────────┤         │
│  │ Guest OS │  │ Guest OS │ ← 每个 VM 一个完整 OS
│  ├──────────┤  ├──────────┤         │
│  │ Hypervisor (VMware/VirtualBox)   │
│  ├──────────────────────────────────┤
│  │        Host OS (Windows/Linux)   │
│  └──────────────────────────────────┘
│                                      │
│             容器架构                 │
├─────────────────────────────────────┤
│  ┌──────┐  ┌──────┐  ┌──────┐      │
│  │ 应用A │  │ 应用B │  │ 应用C │     │
│  ├──────┤  ├──────┤  ├──────┤      │
│  │ Bins │  │ Bins │  │ Bins │      │ ← 共享主机内核
│  └──┬───┘  └──┬───┘  └──┬───┘      │
│     └─────────┼──────────┘          │
│         Docker Engine               │
│  ┌──────────────────────────────────┤
│  │        Host OS                   │
│  └──────────────────────────────────┘

核心区别对比:

特性虚拟机Docker 容器
操作系统每个 VM 有完整的 Guest OS共享主机内核
启动时间分钟级(需启动整个 OS)秒级(只启动进程)
占用空间GB 级(完整的 OS 镜像)MB 级(只含应用和依赖)
性能有损耗(虚拟化硬件)接近原生
隔离性强(完全隔离)中等(共享内核)
移植性差(不同 hypervisor 不同格式)好(OCI 标准)

容器隔离的内部机制:Namespace + Cgroups + UnionFS

容器本质上只是主机上一个被"加固"的普通进程,没有 Hypervisor、没有 Guest 内核。Linux 内核提供三套机制拼出了"像个独立机器"的错觉:

机制负责什么效果
Namespace(命名空间)隔离:PID、NET、MNT、UTS、IPC、USER 六类容器里 ps 只能看到自己的进程(PID 1),看不到主机进程
Cgroups(控制组)限额:CPU、内存、IO、进程数--memory=512m 超了就被 OOM Kill,主机不受牵连
UnionFS(如 OverlayFS)文件系统:镜像层 + 容器可写层叠加见 1.3 的分层镜像与 copy-on-write

⚠️ 理解了"共享内核"就理解了容器与虚拟机的安全边界差异:容器逃逸攻击(如内核漏洞 CVE-2019-5736 针对 runc)的目标是主机内核,而虚拟机被 Hypervisor 硬隔离。这就是多租户场景(如给客户跑不可信代码)依然推荐 microVM 方案(Kata/Firecracker)的原因。

什么时候用 Docker,什么时候用虚拟机?

  • 需要 Linux 桌面环境 → 虚拟机(Docker 没有 GUI)
  • Windows 上跑 Linux 应用 → Docker Desktop(或 WSL2 + Docker)
  • 需要完整的内核隔离 → 虚拟机
  • 微服务部署 → Docker
  • 开发和测试环境 → Docker

1.3 Docker 三大核心概念 ​

1. 镜像(Image) ​

镜像是一个只读的模板,包含运行应用所需的文件系统和配置。

yaml
# 镜像类比:
# 镜像 = 类(Class)—— 是定义和模板
# 容器 = 实例(Instance)—— 是运行中的镜像

# 镜像的层级结构:
FROM node:22-alpine        # 基础镜像(包含 Alpine Linux + Node 22)
WORKDIR /app                # 设置工作目录
COPY package*.json ./       # 复制依赖文件
RUN npm install             # 安装依赖(在镜像中创建一个新层)
COPY . .                    # 复制源码
EXPOSE 3000                 # 声明端口
CMD ["node", "app.js"]      # 默认启动命令

# 每一行指令都会创建一个新层(Layer)
# 层可以被缓存和复用

层的内部机制(为什么镜像又小又快):

  • 每一层是内容寻址的:层的内容算出 SHA256 摘要作为 ID,内容变一点、ID 全变——所以同一个 node:22-alpine 基础层在本机被 10 个镜像共享,只存一份。
  • 运行容器时,Docker(存储驱动 overlay2)在所有只读层顶上叠一个可写层(copy-on-write):改文件时先把文件复制进可写层再改,删除只是打"白out 标记"。这就是 docker rm 容器后数据消失、docker commit 会把可写层固化成新层的原因。
  • 构建缓存同理:docker build 时逐条指令算缓存,某条指令(或其前面的 COPY 内容)变了,从这层开始往后全部重建——这就是 4.3 强调"变化少的指令放前面"的底层原因。
  • 拉取镜像时按层并行下载、校验摘要、已存在就跳过,所以第二次 docker pull 大部分层显示 Already exists。

镜像仓库:Docker Hub(官方)、阿里云 ACR、GitHub Container Registry、Harbor(私有)

2. 容器(Container) ​

容器是镜像的运行实例。你可以对容器进行启动、停止、删除等操作。

bash
# 从镜像启动容器
docker run -d -p 3000:3000 --name my-app node-app

# 查看运行中的容器
docker ps

# 进入容器内部
docker exec -it my-app /bin/sh

# 查看容器日志
docker logs -f my-app

3. 仓库(Repository) ​

仓库用于存储和分发镜像。类似于 Git 仓库管理代码,Docker 仓库管理镜像。

bash
# 拉取镜像
docker pull nginx:latest

# 推送镜像
docker push myusername/my-app:latest

# 搜索镜像
docker search nginx

# 常用仓库地址
# Docker Hub: docker.io/library/nginx
# GitHub: ghcr.io/username/repo
# 私有仓库: registry.example.com/my-app

1.4 Docker 架构 ​

┌──────────────────────────────────────────────────┐
│  Client (docker CLI)                              │
│  docker build / pull / run / push ...              │
│         │                                          │
│         │ REST API (Unix socket / TCP)             │
│         ▼                                          │
│  Docker Daemon (dockerd)                           │
│         │                                          │
│         ├── 管理镜像 (Image Management)              │
│         │    └── 存储驱动 (overlay2/aufs/...)      │
│         ├── 管理容器 (Container Management)         │
│         │    └── 运行时 (runc / containerd)         │
│         └── 管理网络 (Network Management)           │
│              └── CNM (Container Network Model)     │
└──────────────────────────────────────────────────┘

Docker 使用 客户端-服务器架构:

  • Docker Daemon(dockerd):后台服务进程,管理镜像、容器、网络、数据卷
  • Docker Client(docker CLI):用户交互的命令行工具
  • Docker Registry:镜像仓库

containerd 是 Docker 使用的底层容器运行时。自 Docker 1.11+ 起,Docker Daemon 不再直接管理容器生命周期,而是通过 containerd 来实现:

docker build → dockerd → containerd → runc → 创建容器
docker run → dockerd → containerd → runc → 启动容器

runc 是 OCI(Open Container Initiative)标准的参考实现,它负责实际创建和运行容器。


第二篇:安装与环境搭建 ​


2.1 Docker Desktop(Windows/Mac) ​

Docker Desktop 是官方提供的桌面应用,包含 Docker Engine、Docker CLI、Docker Compose、Kubernetes 等所有组件。

Windows 安装 Docker Desktop ​

markdown
前置条件:
1. 64 位 Windows 10/11 Pro/Enterprise/Education(推荐)
2. 开启 WSL2(Windows Subsystem for Linux 2)

安装步骤:
1. 下载 Docker Desktop Installer.exe(from docs.docker.com/get-docker)
2. 双击安装,勾选 "Use WSL 2 based engine"(推荐)
3. 安装完成后重启
4. 打开 Docker Desktop,等待 Engine 启动
5. 任务栏出现 Docker 图标(鲸鱼图标)→ 安装完成

验证安装:
bash
docker version
# Client: Docker Engine - Community
# Server: Docker Engine - Community
# 看到 Client 和 Server 都正常输出即可

docker run hello-world
# Hello from Docker!
# 表示安装成功

Mac 安装 Docker Desktop ​

markdown
1. 下载 Docker Desktop for Mac(Intel / Apple Silicon 版本不同!)
2. 将 Docker.app 拖到 Applications 文件夹
3. 打开 Docker.app,等待启动
4. 菜单栏出现 Docker 图标 → 完成

配置镜像加速 ​

国内访问 Docker Hub 可能较慢,可配置镜像加速器。

注意:自 2024 年 6 月起,国内大量公共 Docker Hub 镜像加速器(Docker 中国、中科大、南京大学等)已陆续关停或转为私有,网上流传的免费加速地址大多已失效;更稳妥的方案是为 Docker daemon 配置代理或自建加速服务。

Docker Desktop Settings → Docker Engine → 编辑 registry-mirrors:

json
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://dockerproxy.com"
  ]
}

2.2 Linux Docker 安装 ​

Ubuntu(推荐) ​

bash
# 1. 卸载旧版本
sudo apt remove docker docker-engine docker.io containerd runc

# 2. 安装依赖
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release

# 3. 添加 Docker 官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 4. 添加稳定版仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. 安装 Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 6. 启动 Docker
sudo systemctl enable docker
sudo systemctl start docker

# 7. 将当前用户加入 docker 组(避免每次 sudo)
sudo usermod -aG docker $USER
# 退出重新登录生效,或 newgrp docker

CentOS / RHEL ​

bash
# 1. 安装 yum-utils
sudo yum install -y yum-utils

# 2. 添加 Docker 仓库
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 3. 安装
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 4. 启动
sudo systemctl enable docker
sudo systemctl start docker

验证 Linux Docker 安装 ​

bash
# 查看 Docker 服务状态
sudo systemctl status docker

# 运行测试容器
docker run hello-world

# 查看版本信息
docker info  # 包含容器数、镜像数、存储驱动等详细信息

Docker Desktop vs Linux Docker 关键区别 ​

markdown
Docker Desktop(Windows/Mac):
- 自带 Docker Compose、Kubernetes
- 包含图形界面(Dashboard)
- 自带容器镜像加速
- 使用虚拟机运行 Linux 容器
- 更适合开发和测试

Linux Docker:
- 仅 Docker Engine(轻量)
- 无图形界面
- 本地运行(无需虚拟化层)
- 性能更好(无虚拟化损耗)
- 更适合生产环境

注意:
- 无 Docker Engine → 无法运行 Docker
- 有 Docker Engine → 可以运行 Docker
- Docker Desktop ≠ Docker Engine(Desktop = Engine + GUI + 工具集)

2.3 WSL2 集成(Windows 最佳实践) ​

在 Windows 上用 Docker 的最佳方式是 Docker Desktop + WSL2 后端。WSL2 让 Windows 上可以直接运行 Linux 内核,Docker 可以利用 WSL2 来运行 Linux 容器。

安装 WSL2 ​

powershell
# PowerShell(管理员)中执行:

# 1. 启用 WSL
wsl --install

# 2. 设置 WSL2 为默认版本
wsl --set-default-version 2

# 3. 安装 Linux 发行版(推荐 Ubuntu)
wsl --install -d Ubuntu

# 4. 查看已安装的 WSL 发行版
wsl -l -v

# 5. 登录 WSL
wsl

Docker Desktop 使用 WSL2 后端 ​

安装 Docker Desktop 时勾选 "Use WSL 2 based engine",或安装后在 Settings → Resources → WSL Integration 中启用。

markdown
启用后:
1. Docker 命令可以直接在 WSL 终端中使用
2. WSL 文件系统的性能远优于 Hyper-V 虚拟机
3. 可以在 WSL 中直接运行 docker 命令

推荐工作流:
- VS Code 连接 WSL(Remote - WSL 插件)
- 在 WSL 中操作 Docker
- 代码保存在 WSL 文件系统中(性能最好)
bash
# 在 WSL Ubuntu 终端中:
docker ps
# 直接可用!不需要再安装 Docker

# 或者:只安装 Docker CLI(推荐在 WSL 中操作)
# 这样 WSL 中可以用 docker 命令,但实际 Docker Engine 运行在 Windows 上

WSL2 vs 纯 Linux 虚拟机对比 ​

特性WSL2虚拟机(VirtualBox/VMware)
启动速度秒级分钟级
内存占用按需分配(~1GB)固定分配(~4GB)
文件系统性能跨 OS 文件操作慢共享文件夹性能中等
集成度高(可直接在 VS Code 中使用)低(需要 SSH)
GPU 支持支持 CUDA需要特殊配置
桌面环境不支持支持

2.4 验证安装 ​

bash
# 1. 版本信息
docker --version

# 2. 详细版本(确认 Client 和 Server 都正常)
docker version

# 3. Docker 系统信息
docker info

# 4. 运行测试容器
docker run hello-world

# 5. 列出镜像
docker images

# 6. 列出容器
docker ps -a

# 7. Docker Compose 版本
docker compose version

版本策略速查(看懂 Docker 的版本号) ​

要点说明
命名规则自 18.03(2018)起改为 MAJOR.MINOR.PATCH 纯版本号,不再有 17.12→18.01 那种日期式版本
当前主线截至 2026-08,Docker Engine 处于 29.x 系列(29.7.2,2026-08-05 发布,见 docs.docker.com/engine/release-notes)
升级红线29.0 起 daemon 要求客户端 API ≥ v1.44(Docker 25.0+),过老的 Client/SDK 连不上新 daemon;且 全新安装默认启用 containerd image store(老 overlay2 存储不变,已有安装不自动切换)
升级建议生产环境锁 minor 版本,升级前通读 release notes 的 breaking changes 一节(29.0 带 CAUTION 标注),先 docker version 比对 Client/Server API 版本差

⚠️ BuildKit 默认化的历史坑:Docker Engine 23.0 起 Linux 上 docker build 默认走 BuildKit/Buildx(旧版需 DOCKER_BUILDKIT=1,更早的 17.05-22.x 要手动开)。老文档里的 DOCKER_BUILDKIT=0 回退方式在新版本已不建议依赖;docker build 是 docker buildx build 的别名,多平台构建(--platform linux/arm64)只能用 buildx 子命令。


第三篇:Docker 基础操作 ​


3.1 镜像管理 ​

拉取镜像 ​

bash
# 从 Docker Hub 拉取
docker pull nginx:latest        # 指定标签
docker pull nginx:1.25          # 指定版本
docker pull nginx               # 不写标签默认为 latest
docker pull node:22-alpine      # Alpine 版(更小,推荐)

# 从私有仓库拉取
docker pull registry.example.com/my-app:v1.0

💡 比 tag 更可靠的定位方式——镜像摘要(digest):docker pull nginx@sha256:3f8a...。tag 是可变的(今天 1.25 和明天可能是两个东西),digest 是内容哈希、永不变。生产部署和 7.1 的 CI 里做"精确回滚/精确复现"时应记录 digest(docker images --digests 查看)。

查看镜像 ​

bash
# 列出所有镜像
docker images

# 或
docker image ls

# 查看镜像详细信息
docker image inspect nginx:latest

# 按条件过滤
docker images --filter "dangling=true"    # 虚悬镜像
docker images --filter "before=nginx:1.25"
docker images --filter "label=maintainer=team-a"

构建镜像 ​

bash
# 从 Dockerfile 构建
docker build -t my-app:v1.0 .             # -t 指定名称:标签
docker build -t my-app:v1.0 -f Dockerfile.prod .  # 指定 Dockerfile 文件名

# 从容器构建(不推荐,自愈性差)
docker commit container-name new-image:tag

删除镜像 ​

bash
# 删除特定镜像
docker rmi nginx:latest

# 删除虚悬镜像
docker image prune

# 删除所有未使用的镜像
docker image prune -a

# 强制删除
docker rmi -f nginx:latest

镜像标签 ​

bash
# 给镜像打标签
docker tag my-app:v1.0 myusername/my-app:v1.0
docker tag my-app:v1.0 myusername/my-app:latest

# 推送标签到仓库
docker push myusername/my-app:v1.0
docker push myusername/my-app:latest

3.2 容器管理 ​

启动容器 ​

bash
# 创建并启动容器
docker run nginx                         # 前台运行(终端被占用)
docker run -d nginx                      # 后台运行(-d = detach)
docker run -d --name web-server nginx    # 指定容器名
docker run -d -p 8080:80 nginx           # 端口映射(主机:容器)
docker run -d -p 8080:80 -p 8443:443 nginx  # 多个端口

# 挂载卷
docker run -d -v /host/data:/container/data nginx  # 绑定挂载
docker run -d -v my-volume:/data nginx              # 卷挂载

# 设置资源限制
docker run -d --memory="512m" --cpus="1.5" nginx

# 设置环境变量
docker run -d -e DB_HOST=localhost -e DB_PORT=3306 my-app

# 进入交互模式
docker run -it ubuntu /bin/bash          # -it = 交互式终端

查看容器 ​

bash
# 运行中容器
docker ps

# 所有容器(包括已停止的)
docker ps -a

# 最近创建的容器
docker ps -l

# 按条件过滤
docker ps --filter "status=exited"
docker ps --filter "name=web"
docker ps --filter "ancestor=nginx"      # 从某镜像创建的

容器生命周期管理 ​

bash
# 停止容器
docker stop container-name
docker stop container-id

# 启动已停止的容器
docker start container-name

# 重启容器
docker restart container-name

# 暂停/恢复
docker pause container-name              # 暂停进程
docker unpause container-name            # 恢复进程

# 删除容器
docker rm container-name                 # 删除已停止的容器
docker rm -f container-name              # 强制删除运行中的容器
docker container prune                   # 删除所有已停止的容器

# 自动重启策略(docker run --restart)
docker run -d --restart=unless-stopped my-app
# no(默认):不重启
# always:退出就重启;Docker 守护进程重启后,曾被 stop 的容器也会重新拉起
# unless-stopped:同 always,但若停止是人为 docker stop 所致,守护进程重启后不再拉起
# on-failure[:max-retries]:仅退出码非 0 时重启

内部机制:容器的"生死"由谁控制?

  1. PID 1 问题:容器内主进程是 PID 1,享有特殊语义——默认不响应没注册 handler 的信号。docker stop 先发 SIGTERM(默认等 10s,-t 可调),超时再发 SIGKILL。如果镜像用 Shell 形式启动(CMD node app.js → PID 1 是 /bin/sh),SIGTERM 根本传不到 node,等于每次都被强杀。这就是 4.1 强调 Exec 形式的原因;语言侧无信号处理时,可用 docker run --init(或 Compose 的 init: true)注入 tini 作为 PID 1 转发信号。
  2. 数据在可写层:容器删除后可写层即销毁(1.3 的 copy-on-write),任何要留下的数据必须落在 Volume/Bind Mount(见 3.3)。
  3. restart 策略 ≠ 健康检查:--restart=always 只管"进程退出后拉起",进程活着但假死(如数据库连接池耗尽卡住)不会触发重启——那需要 HEALTHCHECK(见 4.3),unhealthy 状态可配合 autoheal 类工具或编排系统处理。

进入容器 ​

bash
# 进入运行中的容器(推荐)
docker exec -it container-name /bin/sh     # Alpine 等最小镜像用 sh
docker exec -it container-name /bin/bash   # Ubuntu 等用 bash

# 进入并执行命令
docker exec container-name cat /etc/hosts
docker exec container-name ls -la /app

# 附着到容器(一般用于调试)
docker attach container-name

日志与监控 ​

bash
# 查看容器日志
docker logs container-name                 # 查看日志
docker logs -f container-name              # 实时跟踪(--follow)
docker logs --tail 100 container-name      # 只看最后 100 行
docker logs -t container-name              # 显示时间戳

# 查看容器进程
docker top container-name

# 查看容器资源使用
docker stats                               # 所有容器实时监控
docker stats container-name                # 特定容器

# 查看容器详细信息
docker inspect container-name

# 查看容器的端口映射
docker port container-name

文件复制 ​

bash
# 从主机复制到容器
docker cp /host/file.txt container-name:/app/

# 从容器复制到主机
docker cp container-name:/app/data.json ./data.json

3.3 数据卷(Volume) ​

容器是短暂且无状态的——容器删除后,内部数据会丢失。数据卷提供了持久化存储方案。

三种挂载方式 ​

bash
# 1. 卷(Volume)- 由 Docker 管理,推荐
docker volume create my-volume                     # 创建卷
docker run -v my-volume:/app/data my-app          # 使用卷
docker volume ls                                  # 查看卷列表
docker volume inspect my-volume                   # 查看卷详情
docker volume prune                               # 删除未使用的卷

# 2. 绑定挂载(Bind Mount)- 使用主机路径
docker run -v /absolute/host/path:/app/data my-app

# 3. tmpfs 挂载 - 临时内存存储
docker run --tmpfs /app/temp my-app

三种方式的区别 ​

┌──────────────┬──────────────┬──────────────┬──────────────┐
│              │  Volume      │ Bind Mount   │  tmpfs       │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 存储位置     │ Docker管理   │ 主机任意路径 │ 内存         │
│              │ /var/lib/    │              │              │
│              │ docker/vols  │              │              │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 生命周期     │ Docker管理   │ 手动管理     │ 容器停止清空 │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 持久化       │ 是           │ 是           │ 否           │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 共享         │ 多个容器     │ 多个容器     │ 不可共享     │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 适用场景     │ 数据库数据   │ 开发源码     │ 临时缓存     │
│              │ 配置文件     │ 配置文件     │ session      │
│              │ 持久化数据   │ 热重载调试   │              │
└──────────────┴──────────────┴──────────────┴──────────────┘

卷的内部机制与几个高频坑 ​

  • 卷数据存在哪:命名卷默认在 /var/lib/docker/volumes/<卷名>/_data(Linux);docker volume inspect my-volume 的 Mountpoint 字段就是主机真实路径。卷由 Docker 全权管理,不要在主机上直接改 Mountpoint 里的文件。
  • 首次挂载的空卷会把镜像内容"灌"进卷:如果命名卷是空的,把卷挂到容器内一个已有文件的目录(如 /var/lib/mysql)时,Docker 会先把镜像里该目录的内容复制到卷中;Bind Mount 则直接遮盖主机目录,没有这个行为。
  • ⚠️ 匿名卷陷阱:docker run -v /container/path(只写容器路径不写主机路径)会创建一个匿名卷,docker run 频繁测试后卷会越积越多,docker volume ls -f dangling=true 检查、docker volume prune 清理。
  • ⚠️ Windows/macOS 跨文件系统性能:Bind Mount 主机目录(尤其 Windows 盘挂载进 WSL2 容器)经过 9P/virtiofs 协议,IO 比命名卷慢一个数量级。热重载开发时把代码放在 WSL2 文件系统内或用命名卷,别用 -v C:\code:/app 直挂。
  • 💡 匿名卷的配套语法 -v /app/node_modules(5.2 开发配置里出现过):主机源码挂载到 /app 会遮盖镜像里构建好的 node_modules,再声明一个匿名卷挂到同路径,容器内就用镜像里的依赖、主机目录不受污染——这是 Node 容器热重载的标准写法。

3.4 网络管理 ​

Docker 提供多种网络驱动,让容器之间可以互相通信,也可以与外部通信。

网络驱动类型 ​

bash
# 网络驱动对比
# bridge(默认):单机容器间通信,有 NAT
# host:容器直接使用主机网络,无隔离
# none:无网络
# overlay:跨主机通信(Swarm 集群)

# 查看网络
docker network ls

# 创建网络
docker network create my-network

# 查看网络详情
docker network inspect my-network

bridge 网络(最常用) ​

bash
# 创建自定义 bridge 网络(推荐用自定义而不是默认的 bridge)
docker network create app-network

# 在自定义网络中启动容器
docker run -d --name api --network app-network my-api
docker run -d --name db --network app-network mysql:8.0

# 容器间通过容器名通信(DNS 自动解析)
# api 容器内能通过 db:3306 访问 MySQL,不需要 IP 地址

# 将现有容器加入网络
docker network connect app-network api

# 断开网络
docker network disconnect app-network api

端口映射 ​

bash
# 将容器端口映射到主机端口
docker run -p 8080:80 nginx                 # 主机 8080 -> 容器 80
docker run -p 127.0.0.1:8080:80 nginx       # 只监听本地(安全)
docker run -p 8080:80 -p 8443:443 nginx     # 多个端口映射
docker run -P nginx                         # 随机端口映射(不推荐)

网络内部机制:DNS、iptables 与常见坑 ​

  • 自定义 bridge 才有容器名 DNS:默认 bridge 网络里的容器只能用 IP 互访(--link 是废弃机制);自定义 bridge 网络内置 DNS(127.0.0.11),容器名和服务名自动解析。这是 5.2 里 DB_HOST=postgres 能直接用的原因。
  • 端口映射的底层是 iptables/nftables NAT:-p 8080:80 实际在主机 netfilter 里加了 DNAT 规则。⚠️ 推论:主机上的防火墙规则(UFW/firewalld)管不住已映射的 Docker 端口——Docker 直接改 iptables,ufw deny 8080 可能照样放行。想限制访问,用 127.0.0.1:8080:8080 绑定回环地址或不发布端口。
  • 容器间通信不经过 -p:同一自定义网络内的容器互访走 bridge 内部转发,与端口映射无关——-p 只服务于"主机/外部 → 容器"方向。所以 5.2 的 postgres 开发时映射 5432 是"方便本机连",生产 compose 里数据库可以不写 ports。
  • ⚠️ 改网络配置要重启:docker network connect/disconnect 即时生效,但修改 compose 网络定义(subnet、driver 参数)需 docker compose down && up -d,up -d 不会重连已运行容器的网络。
  • 💡 host 网络(--network host)跳过 NAT 与虚拟网卡,性能最好但零隔离、端口直接占用主机端口,且 Docker Desktop(Windows/Mac)上不适用——容器本来就跑在虚拟机里,host 模式只是 VM 的 host。

第四篇:Dockerfile 编写指南 ​


4.1 Dockerfile 基础指令 ​

总览 ​

dockerfile
# Dockerfile 是从上到下逐条执行的构建脚本
# 每条指令创建一个镜像层(Layer)

# ===== 基础设置 =====
FROM node:22-alpine          # 基础镜像(必须第一行)

# ===== 元数据 =====
LABEL maintainer="team@example.com"
LABEL version="1.0"

# ===== 工作目录 =====
WORKDIR /app                 # 设置工作目录(相当于 cd /app)

# ===== 文件操作 =====
COPY package*.json ./        # 复制文件到镜像
ADD source.tar.gz ./         # 自动解压压缩包

# ===== 运行命令 =====
RUN npm install              # 在构建时执行命令(创建新层)

# ===== 环境变量 =====
ENV NODE_ENV=production
ENV PORT=3000

# ===== 暴露端口(仅声明,不实际映射) =====
EXPOSE 3000

# ===== 启动命令 =====
CMD ["node", "app.js"]       # 容器启动时的默认命令(可以被覆盖)
ENTRYPOINT ["node"]          # 固定入口(不可覆盖,但可接收参数)

每条指令的详细说明 ​

FROM:指定基础镜像

dockerfile
# 常用基础镜像
FROM node:22-alpine       # Node.js 最小镜像(~120MB)
FROM node:22-slim         # Node.js 精简版(~180MB)
FROM node:22              # Node.js 完整版(~900MB)
FROM ubuntu:22.04         # Ubuntu 系统
FROM alpine:3.18          # Alpine Linux(~5MB,极简)
FROM python:3.11-slim     # Python
FROM nginx:alpine         # Nginx

# 多阶段构建
FROM node:22-alpine AS builder
FROM nginx:alpine AS runner

RUN:构建时执行命令(重要:每个 RUN 创建一个新层)

dockerfile
# ✅ 好写法:一条 RUN 做完所有操作(减少层数)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    curl \
    git \
    vim && \
    rm -rf /var/lib/apt/lists/*

# ❌ 坏写法:每个操作一个 RUN(层数多,镜像大)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get install -y vim

COPY vs ADD:

dockerfile
# COPY:只复制,不解压(推荐)
COPY package.json .
COPY . .

# ADD:可解压压缩包 + 支持 URL(有安全隐患,除非必要不用)
ADD source.tar.gz /app/
ADD https://example.com/file.tar.gz /app/  # 不推荐

CMD vs ENTRYPOINT:

dockerfile
# CMD:可以被 docker run 命令覆盖
CMD ["node", "app.js"]
# docker run my-image     → node app.js
# docker run my-image ls  → ls(CMD 被覆盖)

# ENTRYPOINT:固定入口
ENTRYPOINT ["node"]
CMD ["app.js"]
# docker run my-image         → node app.js
# docker run my-image server.js → node server.js(CMD 被覆盖,ENTRYPOINT 不变)

# Shell 形式 vs Exec 形式
CMD node app.js          # Shell 形式:/bin/sh -c "node app.js"(支持变量展开)
CMD ["node", "app.js"]   # Exec 形式:直接运行(推荐,信号传递正常)

构建上下文(build context)机制:执行 docker build . 时,Docker 先把当前目录整个发给 daemon,之后 Dockerfile 里的 COPY 只引用这份上下文副本——所以:

  • 被 .dockerignore 排除的文件即使 COPY . . 也不会进镜像,且不改变上下文指纹(改 README 不会让 COPY 层缓存失效,前提是被 ignore 了)。
  • COPY 引用不了上下文之外的路径(COPY ../other /app 非法),多项目共享文件时用 docker build -f ... <公共根目录> 调整上下文根。
  • 上下文过大(忘记排除 node_modules、.git)会让每次 build 先卡几十秒传输——这是"build 慢在第一步"的最常见原因。

完整 Node.js Dockerfile 示例 ​

dockerfile
# 生产环境多阶段构建
# ============ 阶段1:构建应用 ============
FROM node:22-alpine AS builder

WORKDIR /app

# 先复制依赖文件(利用 Docker 缓存层)
COPY package.json package-lock.json ./
RUN npm ci  # 完整安装(构建阶段需要 devDependencies)

# 再复制源码(源码变化时不会破坏上面的缓存)
COPY . .

RUN npm run build

# ============ 阶段2:运行阶段 ============
FROM node:22-alpine AS runner

WORKDIR /app

# 从 builder 阶段复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 3000

# 非 root 用户运行(安全)
USER node

CMD ["node", "dist/main.js"]

4.2 多阶段构建 ​

为什么需要多阶段构建? ​

dockerfile
# ❌ 单阶段构建的问题:
# 开发工具(TypeScript、Git)包含在最终镜像中 → 镜像臃肿
# 需要手动清理构建依赖 → 容易遗漏

FROM node:22
WORKDIR /app
COPY . .
RUN npm install          # 包含 devDependencies
RUN npm run build        # 需要 TypeScript
RUN npm prune --omit=dev  # 手动清理 devDeps
# 最终镜像 ≈ 1.2GB

# ✅ 多阶段构建:
# 阶段1:安装依赖、编译
# 阶段2:只复制编译产物和 runtime 依赖
# 最终镜像只包含运行所需的最小文件
# 最终镜像 ≈ 150MB(减少 85%+)

多阶段构建实战 ​

dockerfile
# ===== 阶段1:依赖安装 =====
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

# ===== 阶段2:构建 =====
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# ===== 阶段3:生产运行 =====
FROM node:22-alpine AS runner
WORKDIR /app

# 复制构建产物和依赖
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules

EXPOSE 3000
USER node
CMD ["node", "dist/main.js"]
dockerfile
# 多阶段也可以用于不同环境
# dev 阶段包含调试工具和热重载
# prod 阶段只包含运行文件

FROM node:22-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./

FROM base AS dev
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]

FROM base AS build
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS prod
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/main.js"]

# docker build --target dev -t my-app:dev .
# docker build --target prod -t my-app:prod .

4.3 Dockerfile 最佳实践 ​

dockerfile
# 1. 使用 .dockerignore 排除不必要文件
# .dockerignore 内容:
# node_modules
# .git
# .env
# *.md
# Dockerfile
# .dockerignore

# 2. 优先使用官方 Alpine 镜像(更小、更安全)
FROM node:22-alpine    # ✅
FROM node:22           # ❌(大一倍)

# 3. 合并 RUN 指令减少层数
RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*   # ✅ 清理缓存
# 每个 RUN、COPY、ADD 都创建一个层

# 4. 利用构建缓存(把变化少的指令放前面)
COPY package.json ./      # ✅ 先复制依赖文件
RUN npm install           # ✅ 只有 package.json 变化时才重新安装
COPY . .                  # ✅ 源码变化不会影响上面缓存的层

# 5. 使用非 root 用户
RUN addgroup -g 1001 appuser && \
    adduser -u 1001 -G appuser -s /bin/sh -D appuser
USER appuser

# 6. 使用健康检查
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 7. 使用精确的基础镜像版本
FROM node:22-alpine             # ✅ 锁定大版本,避免意外升级
FROM node:alpine                # ❌ latest 标签,可能意外更新

# 8. 不要安装不必要的包
RUN npm install --omit=dev  # ✅ 只安装运行时依赖

BuildKit 进阶加速(现代 Docker 的隐藏福利) ​

自 Docker 23.0 起默认构建器就是 BuildKit(见 2.4),它提供一批经典构建器没有的能力,官方文档在 docs.docker.com/build/:

1. 缓存挂载(cache mount)——包管理器缓存跨构建复用,且不进镜像层:

dockerfile
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
    npm ci --no-audit --no-fund
# apt 版:RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
#         apt-get install -y curl

普通 RUN npm ci 每次依赖清单变化都要重新从 registry 下载 tarball;cache mount 让下载缓存常驻 BuildKit,重建时只安装不解包网络。⚠️ 注意 sharing=locked 可避免并行构建争抢同一缓存目录。

2. 构建缓存导出到远端——CI 里最实用,解决"每次 runner 都是新机器、缓存全丢":

bash
docker buildx build \
  --cache-from type=registry,ref=my-org/my-app:buildcache \
  --cache-to   type=registry,ref=my-org/my-app:buildcache,mode=max \
  -t my-app:1.2.3 .

3. 查看/清理构建缓存:docker buildx du(看占用)、docker buildx prune --keep-storage 10GB(限额清理)。BuildKit 缓存默认也会自动 GC,但上限不小——CI 机磁盘被 build cache 撑爆是常见事故。

4. Secrets 挂载——构建时注入密钥且不留层(详见 8.1):

dockerfile
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci

第五篇:Docker Compose ​


5.1 Compose 文件结构 ​

Docker Compose 用于定义和运行多容器 Docker 应用。用一个 docker-compose.yml 文件描述所有服务。

docker-compose.yml 基础结构 ​

yaml
version: "3.8"                   # Compose 文件格式版本

services:                        # 定义应用的服务
  web:                           # 服务名(同时也是容器 DNS 名)
    build: .                     # 从 Dockerfile 构建
    image: my-app:latest         # 或者直接使用已存在的镜像
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    depends_on:
      - db

  db:
    image: mysql:8.0
    volumes:
      - db-data:/var/lib/mysql

volumes:                         # 定义卷
  db-data:

networks:                        # 定义网络
  app-network:

Compose 文件版本 ​

yaml
# Docker Compose v2(docker compose,推荐)
# 不再需要 version 字段(已废弃)
services:
  web:
    image: nginx
# docker compose up 即可

# Docker Compose v1(docker-compose,旧版)
version: "3.8"
services:
  web:
    image: nginx
# docker-compose up(有连字符)

depends_on 的真相:只控制"启动顺序",不控制"就绪顺序" ​

初学者最常见的误解:depends_on: - db 之后后端启动时数据库就一定能连上。不是——短语法只保证 db 容器先创建并启动,Postgres 镜像的初始化(建库、跑 initdb)往往还要几秒到几十秒,后端一连就是 connection refused。

Compose 规范给出三种 condition(长语法):

condition等待条件典型用途
service_started依赖容器已启动(等价短语法)只要顺序,应用自己会重试连接
service_healthy依赖容器的 healthcheck 通过数据库/缓存等重型依赖(5.2 的 postgres 用法)
service_completed_successfully依赖容器运行到退出码 0一次性任务:migration、seed 数据
yaml
services:
  migrate:
    build: .
    command: npm run migrate          # 跑完就退出
  backend:
    build: .
    depends_on:
      migrate:
        condition: service_completed_successfully   # 等迁移成功完成
      db:
        condition: service_healthy                  # 等数据库健康

⚠️ service_healthy 依赖被等待方定义了 healthcheck,否则 Compose 报配置错误;且 healthcheck 必须"最终能变 healthy",写错的探测命令会让 up 永远卡在 waiting。应用侧仍建议保留连接重试逻辑——condition 只覆盖 compose up 时刻,运行期数据库重启不保证重新排序。


5.2 常用配置示例 ​

完整的 Web 应用 ​

yaml
services:
  # ==== 前端服务 ====
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - backend
    networks:
      - app-network

  # ==== 后端 API ====
  backend:
    build:
      context: ./backend
      target: runner          # 多阶段构建指定阶段
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_PORT=5432
      - REDIS_HOST=redis
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - ./backend:/app        # 开发时挂载源码实现热重载
      - /app/node_modules     # 排除 node_modules
    networks:
      - app-network

  # ==== 数据库 ====
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: ${DB_PASSWORD}  # 从 .env 文件读取
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # ==== 缓存 ====
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    networks:
      - app-network

  # ==== 消息队列 ====
  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq

volumes:
  postgres-data:
  redis-data:
  rabbitmq-data:

networks:
  app-network:
    driver: bridge

.env 文件 ​

bash
# .env(与 docker-compose.yml 同目录)
DB_PASSWORD=mysecretpassword
RABBITMQ_PASSWORD=anothersecret
API_KEY=dev-key-123

开发环境 vs 生产环境 ​

yaml
# docker-compose.yml(基础配置)
services:
  app:
    build: .
    ports:
      - "3000:3000"

# docker-compose.override.yml(开发环境,自动覆盖)
# docker compose up 默认会加载 override 文件
services:
  app:
    build:
      context: .
      target: dev
    volumes:
      - .:/app
    environment:
      - NODE_ENV=development

# docker-compose.prod.yml(生产环境)
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up
services:
  app:
    build:
      context: .
      target: runner
    environment:
      - NODE_ENV=production
    restart: always

5.3 Compose 实战:全栈项目 ​

项目结构 ​

my-fullstack-app/
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── nginx/
│   └── nginx.conf
├── docker-compose.yml
├── docker-compose.prod.yml
└── .env

开发环境 Compose ​

yaml
# docker-compose.yml(开发环境)
services:
  # 前端(Vite 开发服务器,热重载)
  frontend:
    build:
      context: ./frontend
      target: dev
    ports:
      - "5173:5173"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    depends_on:
      - backend

  # 后端 API
  backend:
    build:
      context: ./backend
      target: dev
    ports:
      - "3000:3000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - DATABASE_URL=postgresql://myapp:${DB_PASSWORD}@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  # 数据库
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    ports:
      - "5432:5432"              # 方便本地连接
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 5s
      timeout: 5s
      retries: 5

  # 缓存
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:

开发时常用 Compose 命令 ​

bash
# 启动所有服务(前台)
docker compose up

# 后台启动
docker compose up -d

# 查看日志
docker compose logs -f

# 查看特定服务日志
docker compose logs -f backend

# 重新构建并启动
docker compose up --build

# 停止所有服务
docker compose down

# 停止并删除卷(数据也会删!)
docker compose down -v

# 查看运行中的服务
docker compose ps

# 进入某个服务容器
docker compose exec backend /bin/sh

# 执行一次性命令
docker compose run --rm backend npm test

第六篇:项目开发到部署全流程 ​


6.1 本地开发环境搭建 ​

使用 Docker 搭建开发环境的最大优势是:新成员加入 5 分钟即可开始编码。

开发工作流 ​

bash
# 1. 克隆代码
git clone git@github.com:team/my-project.git
cd my-project

# 2. 首次启动(构建并启动所有服务)
docker compose up -d                   # 后台启动

# 3. 启动时自动执行:
# - 拉取/构建镜像
# - 创建网络
# - 创建卷
# - 按依赖顺序启动服务

# 4. 查看服务是否启动成功
docker compose ps

# 5. 查看日志确认
docker compose logs -f backend

# 6. 数据库迁移
docker compose exec backend npm run migrate

# 7. 开始开发!
# 修改源码 → 自动热重载(开发模式挂载了卷)

# 8. 测试
docker compose exec backend npm test

# 9. 停止
docker compose down

开发环境的关键配置 ​

yaml
# docker-compose.override.yml(开发环境自动加载)
services:
  backend:
    # 挂载源码实现热重载
    volumes:
      - ./backend:/app
      - /app/node_modules
    # 开发模式端口暴露
    ports:
      - "9229:9229"  # Node.js 调试端口
    # 开发环境变量
    environment:
      - NODE_ENV=development
      - DEBUG=true

6.2 生产环境镜像构建 ​

生产构建策略 ​

bash
# 版本号策略:
# - latest:当前最新版本
# - 语义版本:1.2.3
# - Git SHA:git-abc1234(可追溯)
# - 时间戳:20240101-1200

# 推荐使用双重标签
docker build -t my-app:1.2.3 -t my-app:latest .
docker push my-app:1.2.3
docker push my-app:latest

Node.js 生产镜像优化 ​

dockerfile
# Dockerfile(生产,多阶段构建)
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

# 只复制生产需要的文件
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./

USER nodejs
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "dist/main.js"]

镜像体积优化对比 ​

bash
# 不同基础镜像大小
node:22                        # ~950MB
node:22-slim                   # ~180MB  ✅ 常用
node:22-alpine                 # ~120MB  ✅ 推荐

# 多阶段构建效果
# 单阶段(包含构建工具):~1.2GB
# 多阶段(只包含运行文件):~150MB
# 多阶段 + Alpine + 压缩:~80MB

# 查看镜像层级
docker history my-app:latest

6.3 镜像仓库与分发 ​

Docker Hub(公共仓库) ​

bash
# 登录
docker login

# 打标签并推送
docker tag my-app:latest username/my-app:1.0.0
docker push username/my-app:1.0.0

# 拉取
docker pull username/my-app:1.0.0

GitHub Container Registry(GHCR) ​

bash
# 登录(需要 GitHub Token)
echo $GITHUB_TOKEN | docker login ghcr.io -u $USERNAME --password-stdin

# 打标签
docker tag my-app:latest ghcr.io/my-org/my-app:1.0.0

# 推送
docker push ghcr.io/my-org/my-app:1.0.0

私有仓库(Harbor) ​

yaml
# docker-compose.yml 搭建 Harbor(企业级镜像仓库)
services:
  harbor:
    image: goharbor/harbor-core:v2.8.0
    # Harbor 安装需要 docker-compose,此处仅示意

镜像清理策略 ​

bash
# 本地清理
docker image prune -a --filter "until=24h"   # 清理 24 小时前的镜像

# 定期删除
# crontab 定时任务
0 3 * * * docker image prune -a -f --filter "until=168h"  # 每周清理

多架构镜像(x86 与 ARM 一份 tag 通吃) ​

Apple Silicon、树莓派、AWS Graviton 都是 arm64;传统服务器是 amd64。用 buildx 一次构建、一次推送,仓库里以 manifest list(镜像索引) 把同一个 tag 指向多架构变体,客户端 docker pull 时按本机平台自动选层:

bash
docker buildx create --use                       # 首次:建一个支持多平台的 builder
docker buildx build --platform linux/amd64,linux/arm64 \
  -t ghcr.io/my-org/my-app:1.0.0 --push .

⚠️ 多平台构建时 --load(导入本地 docker)只能单平台,多平台必须 --push 或用 --output type=oci;QEMU 模拟跨架构编译很慢,重项目考虑原生 runner 或 remote builder。docker buildx ls 查看当前 builder 支持的平台。


6.4 服务器部署 ​

生产环境 Compose 配置 ​

yaml
# docker-compose.prod.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: my-app:latest
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
    restart: always                              # 自动重启
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.5"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    healthcheck:
      test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
      - certbot-data:/var/www/certbot
    depends_on:
      - app
    restart: always

volumes:
  certbot-data:

部署流程 ​

bash
# 手动部署(简单项目)

# 1. 本地构建并推送
docker build -t my-app:1.0.0 .
docker tag my-app:1.0.0 registry.example.com/my-app:1.0.0
docker push registry.example.com/my-app:1.0.0

# 2. SSH 到服务器
ssh user@your-server.com

# 3. 拉取新镜像
docker pull registry.example.com/my-app:1.0.0

# 4. 启动新版本
docker compose -f docker-compose.prod.yml up -d

# 零停机部署(滚动更新)
# docker compose up -d 会自动替换容器,但需要负载均衡

Nginx 反向代理配置 ​

nginx
# nginx.conf
upstream app {
    server app:3000;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /ws {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

第七篇:CI/CD 集成 ​


7.1 GitHub Actions + Docker ​

完整 CI/CD 流水线 ​

yaml
# .github/workflows/deploy.yml
name: Build, Test, and Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ===== 1. 测试 =====
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_DB: test
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "npm"

      - run: npm ci
      - run: npm run lint
      - run: npm run test -- --coverage

  # ===== 2. 构建并推送镜像 =====
  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=sha,format=long
            type=ref,event=branch
            type=raw,value=latest

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ===== 3. 部署到服务器 =====
  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to production
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          script: |
            cd /app
            docker compose pull
            docker compose up -d --remove-orphans
            docker system prune -af --filter "until=24h"

这条流水线里值得深挖的三个机制 ​

  1. secrets.GITHUB_TOKEN 与 permissions:每个 job 自动获得一个短期 JWT。默认权限是只读的,所以推 GHCR 必须显式声明 packages: write——这是最小权限原则在 CI 里的落地,比配置长期 PAT 安全得多(GHCR 匿名拉取公开镜像不需要登录)。
  2. cache-from/cache-to: type=gha:把 BuildKit 层缓存存进 GitHub Actions 的缓存服务(每个仓库有 10GB 配额,LRU 逐出),下个 job 构建时恢复。mode=max 会缓存所有中间阶段而不只是最终镜像层——多阶段构建的 deps/builder 阶段产物也能命中,通常能把重建时间从分钟级压到十几秒。
  3. --remove-orphans:docker compose up -d 只处理当前 compose 文件里声明的服务;如果上一版 compose 里有个 worker 服务这次被删了,它会作为"孤儿容器"继续运行占资源。带 -d 的 up 加上 --remove-orphans 才会把它停掉——做"配置即部署"时这是个容易被忽略的一致性缺口。

⚠️ docker compose pull + up -d 的组合依赖镜像 tag 不变(如 :latest)。如果按 6.2 的双重标签策略部署不可变 tag,服务器上的 compose 文件里 image 字段需要同步更新(常见做法:CI 用 sed/compose -f 环境变量注入新版本号),否则 pull 了新版但 up 起的还是旧 tag 容器。


7.2 GitLab CI + Docker ​

yaml
# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  DOCKER_HOST: tcp://docker:2375
  DOCKER_TLS_CERTDIR: ""
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

services:
  - docker:dind                         # Docker-in-Docker

# ===== 测试 =====
test:
  stage: test
  image: node:22-alpine
  services:
    - postgres:15-alpine
    - redis:7-alpine
  variables:
    DATABASE_URL: postgresql://test:test@postgres:5432/test
    REDIS_URL: redis://redis:6379
  script:
    - npm ci
    - npm run lint
    - npm run test

# ===== 构建 =====
build:
  stage: build
  image: docker:24
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $IMAGE_TAG .
    - docker push $IMAGE_TAG

# ===== 部署 =====
deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
  script:
    - ssh $DEPLOY_USER@$DEPLOY_HOST "
        docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY &&
        docker pull $IMAGE_TAG &&
        docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest &&
        docker compose up -d
      "
  only:
    - main

7.3 自动化部署流水线 ​

完整的部署策略 ​

markdown
1. 开发流程
   开发者 push 到 feature 分支
   → GitHub Actions 运行测试
   → 测试通过后,创建 Pull Request
   → Code Review 通过后,合并到 main

2. 持续集成(CI)
   合并到 main 后自动触发:
   → 运行完整测试套件(单元测试 + 集成测试 + E2E)
   → 构建 Docker 镜像
   → 推送镜像到 GHCR / Harbor
   → 打版本标签(git tag)

3. 持续部署(CD)
   推送新镜像后:
   → 通过 SSH 连接到生产服务器
   → 拉取最新镜像
   → 使用 docker compose 启动新容器
   → 健康检查(Healthcheck)
   → 如果检查失败,自动回滚到上一个版本

4. 蓝绿部署(可选)
   同时保留两个环境:
   - blue(当前生产)
   - green(新版本)
   切换负载均衡指向 green
   验证通过后,移除 blue
   green 变为新的 blue

Docker 部署脚本 ​

bash
#!/bin/bash
# deploy.sh - 服务器部署脚本

set -e

APP_NAME="my-app"
IMAGE_NAME="ghcr.io/my-org/$APP_NAME:latest"
COMPOSE_FILE="docker-compose.prod.yml"
SERVICE="app"              # Compose 文件中的服务名

echo "=== 开始部署 $APP_NAME ==="

# 1. 备份当前运行版本(在拉取新镜像之前)
echo "备份当前版本..."
docker tag $IMAGE_NAME ${IMAGE_NAME}:backup || true

# 2. 拉取最新镜像
echo "拉取镜像..."
docker pull $IMAGE_NAME

# 3. 启动新版本
echo "启动新版本..."
docker compose -f $COMPOSE_FILE up -d --no-deps $SERVICE

# 4. 健康检查(Compose v2 容器名为 <项目名>-<服务名>-<编号>,用 ps -q 获取容器 ID)
echo "健康检查..."
CONTAINER=$(docker compose -f $COMPOSE_FILE ps -q $SERVICE)
for i in {1..30}; do
  STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER" 2>/dev/null || echo "starting")
  if [ "$STATUS" = "healthy" ]; then
    echo "健康检查通过!"
    break
  fi
  echo "等待服务启动... ($i/30)"
  sleep 2
done

if [ "$STATUS" != "healthy" ]; then
  echo "健康检查失败!回滚到上一个版本..."
  docker tag ${IMAGE_NAME}:backup $IMAGE_NAME
  docker compose -f $COMPOSE_FILE up -d --no-deps $SERVICE
  exit 1
fi

# 5. 清理旧镜像
echo "清理旧镜像..."
docker image prune -af --filter "until=168h"

echo "=== 部署完成 ==="

第八篇:进阶话题 ​


8.1 Docker 安全最佳实践 ​

dockerfile
# 1. 使用非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs
USER nodejs

# 2. 使用只读文件系统
# docker run --read-only --tmpfs /tmp my-app
# 镜像文件系统只读,写入需要 tmpfs 或 volume

# 3. 限制容器资源
# docker run --memory="256m" --cpus="0.5" --pids-limit=100 my-app

# 4. 不要泄露 secrets
# ❌ 不要写在 Dockerfile 中
ENV DB_PASSWORD=mysecret   # bad
# ✅ 使用 --env-file 或 secrets
# docker run --env-file .env my-app

补充:构建期密钥与运行期降权 ​

dockerfile
# ❌ 经典反模式:构建参数会留在 docker history 里
ARG API_TOKEN && npm config set //registry.npmjs.org/:_authToken=$API_TOKEN
# ✅ BuildKit secret 挂载:文件出现在 /run/secrets/<id>,只在 RUN 期间存在,不落层
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
# 用法:docker build --secret id=npm_token,src=$HOME/.npmrc -t my-app .
bash
# 运行期最小权限(docker run,官方 runtime privilege 文档)
docker run --security-opt no-new-privileges=true \  # 禁止程序通过 setuid 提权
           --cap-drop=ALL --cap-add=NET_BIND_SERVICE \  # 丢掉全部 Linux capabilities,只留绑定 80 端口
           --read-only --tmpfs /tmp \
           my-app

⚠️ --privileged 会把主机所有设备与 capability 交给容器,等价于放弃隔离,只在调试硬件/内核类场景临时使用,绝不能进生产 Compose。 💡 镜像漏洞扫描:docker scout cves my-app:latest(Docker Desktop 内置、Engine 可作为 CLI 插件安装)可直接列出镜像里的 CVE 及修复版本,接入 7.1 的 CI 做质量门禁;或离线场景用 Trivy 等工具。


8.2 容器编排(Kubernetes 简介) ​

Kubernetes(K8s)用于管理大规模容器集群。当你需要管理数十个以上容器时,Docker Compose 已不够用。

yaml
# Kubernetes Deployment(与 Compose 的对比)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3                    # 3 个副本
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-app:latest
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: "production"
          resources:
            limits:
              memory: "256Mi"
              cpu: "500m"

---
# Service(暴露服务)
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 3000

Docker Compose vs Kubernetes ​

特性Docker ComposeKubernetes
适用场景单机、开发测试大规模集群
容器编排简单(单机)复杂(集群调度)
高可用手动管理自动(负载均衡、自愈)
滚动更新手动自动滚/回滚
学习成本低高

8.3 监控与日志 ​

bash
# 日志管理
docker logs --tail 100 -f container-name

# 集中日志:ELK Stack(Elasticsearch + Logstash + Kibana)
# 或使用 Docker 的 logging driver 直接发送
docker run --log-driver=awslogs --log-opt awslogs-group=my-app my-image

# 监控容器指标
docker stats
# 持续监控 → Prometheus + Grafana

8.4 性能调优 ​

markdown
1. 镜像优化
   - 使用 Alpine 基础镜像
   - 多阶段构建
   - 合并 RUN 指令

2. 容器优化
   - 设置合理的内存和 CPU 限制
   - 使用 --restart=always 自动重启

3. 缓存优化
   - 利用 Docker 构建缓存(合理安排 COPY 顺序)
   - 使用 BuildKit 加速(DOCKER_BUILDKIT=1)

4. Docker Daemon 优化
   - 使用 overlay2 存储驱动(推荐)
   - 配置镜像加速器
   - 设置日志大小限制

基于 VitePress 构建的个人学习文档知识库