Docker 从入门到部署实战

Docker 从入门到部署实战


1. Docker基础

1.1 Docker 基础概念

1.1.1 容器化

容器化是一种轻量级的虚拟化技术,将应用程序及其所有依赖项打包到一个**标准化的单元(容器)**中,确保应用在任何环境中都能一致运行。

核心思想:"Build once, Run anywhere"

graph LR A[开发者编写代码] --> B[打包为容器镜像] B --> C[开发环境运行] B --> D[测试环境运行] B --> E[生产环境运行] style B fill:#0db7ed,color:#fff

1.1.2 容器 vs 虚拟机

graph TB subgraph 虚拟机架构 direction TB H1[物理硬件] HV[Hypervisor 虚拟机管理程序] subgraph VM1[虚拟机1] G1[Guest OS] B1[Bins/Libs] A1[App A] end subgraph VM2[虚拟机2] G2[Guest OS] B2[Bins/Libs] A2[App B] end H1 --> HV HV --> VM1 HV --> VM2 end subgraph 容器架构 direction TB H2[物理硬件] OS[Host OS 宿主操作系统] DE[Docker Engine] subgraph C1[容器1] CB1[Bins/Libs] CA1[App A] end subgraph C2[容器2] CB2[Bins/Libs] CA2[App B] end H2 --> OS OS --> DE DE --> C1 DE --> C2 end
特性 容器 虚拟机
启动速度 秒级 分钟级
体积 MB 级 GB 级
性能 接近原生 有损耗
隔离性 进程级隔离 完全隔离
操作系统 共享宿主内核 独立 Guest OS
资源占用 极少 较多
单机数量 数百个 通常几十个

1.1.3 Docker 架构

Docker 采用 C/S(客户端-服务端)架构

graph LR subgraph Client[Docker Client 客户端] CMD["docker build
docker pull
docker run
docker ps
..."] end subgraph Host[Docker Host 宿主机] DAEMON[Docker Daemon
dockerd] subgraph Images[镜像] IMG1[nginx:latest] IMG2[node:18] IMG3[mysql:8.0] end subgraph Containers[容器] CON1[web-server] CON2[api-app] end DAEMON --> Images DAEMON --> Containers end subgraph Registry[Registry 镜像仓库] DH[Docker Hub] PR[私有仓库] end Client -->|REST API| DAEMON DAEMON -->|pull / push| Registry

三大核心组件:

组件 说明
Docker Client 用户与 Docker 交互的命令行工具,发送命令给 Daemon
Docker Daemon 后台守护进程,负责构建、运行和管理容器
Docker Registry 存储和分发镜像的仓库服务

1.1.4 核心概念

1.1.4.1 镜像(Image)

镜像是一个只读模板,包含运行应用所需的一切:代码、运行时、库、环境变量、配置文件。

graph TB subgraph 镜像分层结构 direction TB L1[Layer 4: 应用代码 COPY . /app] L2[Layer 3: 安装依赖 RUN npm install] L3[Layer 2: 设置工作目录 WORKDIR /app] L4[Layer 1: 基础镜像 FROM node:18-alpine] L1 --> L2 --> L3 --> L4 end style L1 fill:#0db7ed,color:#fff style L2 fill:#1a9bd7,color:#fff style L3 fill:#2980b9,color:#fff style L4 fill:#3867a8,color:#fff

关键特性:

  • 分层存储:每一层都是只读的,层与层之间可复用,节省磁盘空间
  • 写时复制(Copy-on-Write):容器运行时在最上层添加可写层
  • 通过 Dockerfile 构建:使用声明式语法定义镜像内容

常用命令:

bash 复制代码
docker images                  # 列出本地镜像
docker pull nginx:latest       # 从仓库拉取镜像
docker rmi nginx:latest        # 删除镜像
docker build -t myapp:1.0 .    # 构建镜像
docker tag myapp:1.0 myapp:v1  # 为镜像创建新的标签引用,不会复制镜像,两个标签指向同一镜像 ID
docker image prune             # 删除所有无标签(<none>)的悬空镜像,加 -a 可删除所有未被容器引用的镜像
1.1.4.2 容器(Container)

容器是镜像的运行实例,拥有自己的文件系统、网络、进程空间。

stateDiagram-v2 [*] --> Created: docker create Created --> Running: docker start Running --> Paused: docker pause Paused --> Running: docker unpause Running --> Stopped: docker stop Stopped --> Running: docker start Running --> Stopped: docker kill Stopped --> Deleted: docker rm Deleted --> [*]

常用命令:

bash 复制代码
docker run -d --name web -p 80:80 nginx    # 基于 nginx 镜像创建名为 web 的容器,后台运行并将宿主机 80 端口映射到容器 80 端口
docker ps                                   # 查看运行中的容器
docker ps -a                                # 查看所有容器(含停止的)
docker stop web                             # 停止容器
docker start web                            # 启动容器
docker restart web                          # 重启容器
docker rm web                               # 删除容器
docker exec -it web bash                    # 进入容器交互终端
docker logs -f web                          # 查看容器日志(持续输出)

docker run 常用参数:

参数 说明 示例
-d 后台运行 docker run -d nginx
-p 端口映射 -p 8080:80(宿主机:容器)
--name 容器名称 --name my-nginx
-v 挂载数据卷:将宿主机目录映射到容器内目录,实现数据持久化和文件共享(容器删除后数据不丢失) -v /host/path:/container/path
-e 设置容器内的环境变量,容器内的应用可通过 process.env(Node)或 os.environ(Python)等方式读取;常用于传递数据库密码、端口号等配置,无需修改镜像即可改变应用行为 -e MYSQL_ROOT_PASSWORD=123
--rm 容器停止后自动删除该容器(不会删除镜像),常用于临时测试场景 docker run --rm nginx
--network 将容器加入指定的 Docker 网络,同一网络内的容器可通过容器名互相访问(如 web 容器访问 db 容器) --network my-net
-it -i 保持标准输入打开 + -t 分配伪终端,组合使用可进入容器内的交互式命令行(类似 SSH 进入服务器) docker run -it ubuntu bash
1.1.4.3 仓库(Registry)

Registry 是集中存储和分发镜像的服务。

graph TB subgraph 公共仓库 DH[Docker Hub
hub.docker.com] GH[GitHub Container Registry
ghcr.io] AL[阿里云镜像仓库
registry.cn-hangzhou.aliyuncs.com] end subgraph 私有仓库 HR[Harbor] NX[Nexus] RE[Docker Registry] end DEV[开发者] -->|docker push| DH DEV -->|docker pull| DH DEV -->|docker push| HR DEV -->|docker pull| HR

镜像命名规范:

bash 复制代码
[仓库地址/]命名空间/镜像名:标签

示例:
nginx:latest                                       # 官方镜像,默认 Docker Hub
library/nginx:1.25                                 # 完整官方路径
registry.cn-hangzhou.aliyuncs.com/myns/myapp:v1.0  # 阿里云私有仓库

常用命令:

bash 复制代码
docker login                        # 登录 Docker Hub
docker login registry.example.com   # 登录私有仓库
docker push myrepo/myapp:1.0       # 推送镜像到仓库
docker pull myrepo/myapp:1.0       # 从仓库拉取镜像
docker search nginx                 # 搜索 Docker Hub 上的镜像

1.2 环境搭建(Windows + WSL Ubuntu)

1.2.1 整体架构

graph TB subgraph Windows[Windows 宿主机] PS[PowerShell / Terminal] end subgraph WSL2[WSL 2 - Ubuntu] subgraph DockerEngine[Docker Engine] CLI[Docker Client
docker CLI 命令行工具] DAEMON[Docker Daemon
dockerd 后台守护进程] RT[containerd + runc
容器运行时] CLI -->|REST API| DAEMON DAEMON --> RT end subgraph Containers[运行中的容器] C1[nginx] C2[mysql] C3[node-app] end RT --> Containers end PS -->|wsl 命令进入| WSL2

不安装 Docker Desktop,直接在 WSL 2 Ubuntu 中安装 Docker Engine,轻量且免费。

Docker Engine 包含的组件:

组件 包名 说明
Docker Client docker-ce-cli 命令行工具,用户输入 docker 命令时调用的就是它
Docker Daemon docker-ce 后台守护进程 dockerd,负责管理镜像、容器、网络、存储卷
containerd containerd.io 容器运行时,负责容器的生命周期管理(创建、启动、停止)
BuildKit docker-buildx-plugin 新一代镜像构建引擎
Compose docker-compose-plugin 多容器编排工具,通过 docker compose 命令使用

1.2.2 Docker 安装

步骤一:启用 WSL 2

管理员身份打开 PowerShell:

powershell 复制代码
# 启用 WSL 功能
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

# 启用虚拟机平台
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

# 重启电脑后,设置 WSL 默认版本为 2
wsl --set-default-version 2

# 安装 Ubuntu(如果尚未安装)
wsl --install -d Ubuntu

# 查看已安装的发行版及 WSL 版本(确认 VERSION 为 2)
wsl -l -v
步骤二:在 WSL Ubuntu 中安装 Docker Engine
bash 复制代码
# 进入 WSL Ubuntu
wsl -d Ubuntu

# 卸载旧版本(如有)
sudo apt-get remove docker docker-engine docker.io containerd runc

# 更新本地包索引(从软件源同步最新的可用包列表)
sudo apt-get update

# 安装添加 Docker 仓库源所需的依赖工具,-y 表示自动确认安装
sudo apt-get install -y \
    ca-certificates \  # CA 根证书,用于 HTTPS 请求时验证服务器证书的合法性
    curl \             # HTTP 命令行工具,用于下载 Docker 的 GPG 密钥
    gnupg \            # GPG 加密工具,用于验证下载的软件包签名是否可信
    lsb-release        # 提供 Linux 发行版信息(如版本代号),用于拼接正确的仓库地址

# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# 添加 Docker 仓库源
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

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

# 将当前用户加入 docker 组(免 sudo 执行 docker 命令)
sudo usermod -aG docker $USER

# 重新登录 WSL 使用户组权限生效
exit
wsl -d Ubuntu
步骤三:设置 Docker 服务自动启动

WSL 2 默认不使用 systemd,需要手动启动 Docker 服务或开启 systemd。

systemd 是 Linux 的系统和服务管理器,负责在系统启动时自动拉起各种后台服务(如 Docker Daemon、网络、日志等)。类似 Windows 的"服务管理器"(services.msc),开启后 Docker 服务会随系统启动自动运行,无需每次手动执行 sudo service docker start

方式一:开启 WSL systemd 支持(推荐)

bash 复制代码
# 编辑 WSL 配置文件
sudo tee /etc/wsl.conf <<-'EOF'
[boot]
systemd=true
EOF

然后在 PowerShell 中重启 WSL:

powershell 复制代码
wsl --shutdown
wsl -d Ubuntu

重启后 Docker 会随 systemd 自动启动。

方式二:每次手动启动

bash 复制代码
# 启动 Docker 服务
sudo service docker start

1.2.3 镜像加速配置

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

在 WSL Ubuntu 中配置:

bash 复制代码
# 创建或编辑 daemon.json
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me",
    "https://docker.rainbond.cc"
  ]
}
EOF

# 重启 Docker 服务
sudo systemctl daemon-reload
sudo systemctl restart docker

提示 :镜像加速地址可能会变化失效,可关注 docker-practice/docker-registry-cn-mirror-test 获取最新可用地址。

1.2.4 验证安装

基本验证
bash 复制代码
# 查看 Docker 版本
docker --version
# 输出示例:Docker version 27.x.x, build xxxxxxx

# 查看详细版本信息(Client + Server)
docker version

# 查看 Docker 系统信息
docker info
运行测试容器
bash 复制代码
# 运行 hello-world 测试镜像
docker run hello-world

看到以下输出说明安装成功:

css 复制代码
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
验证镜像加速是否生效
bash 复制代码
# 查看 docker info 中的 Registry Mirrors 字段
docker info | grep -A 5 "Registry Mirrors"

# 输出示例:
#  Registry Mirrors:
#   https://docker.1ms.run/
#   https://docker.xuanyuan.me/
#   https://docker.rainbond.cc/
运行一个实际容器验证
bash 复制代码
# 运行 nginx 并映射端口
docker run -d --name test-nginx -p 8080:80 nginx

# 访问测试
curl http://localhost:8080
# 应返回 nginx 欢迎页 HTML

# 查看运行中的容器
docker ps

# 清理测试容器
docker stop test-nginx && docker rm test-nginx

1.3 基础命令

1.3.1 镜像管理

命令 说明 示例
docker search 搜索镜像 docker search nginx
docker pull 拉取镜像(默认 latest) docker pull nginx:1.25-alpine
docker images 列出本地镜像 docker images -q(仅显示 ID)
docker inspect 查看镜像详细信息 docker inspect nginx:latest
docker history 查看镜像构建历史(各层信息) docker history nginx:latest
docker tag 给镜像打标签 docker tag nginx:latest myrepo/nginx:v1
docker push 推送镜像到仓库 docker push myrepo/nginx:v1
docker save 导出镜像为 tar 文件 docker save -o nginx.tar nginx:latest
docker load 从 tar 文件加载镜像 docker load -i nginx.tar
docker rmi 删除镜像 docker rmi nginx:latest
docker image prune 清理悬空镜像(无标签 <none> docker image prune -a(清理所有未引用镜像)

1.3.2 容器管理

命令 说明 示例
docker run 创建并启动容器 docker run -d --name web -p 8080:80 nginx
docker create 仅创建容器(不启动) docker create --name web nginx
docker ps 查看容器 docker ps -a(含停止的) docker ps -q(仅 ID)
docker start 启动容器 docker start web
docker stop 停止容器 docker stop web
docker restart 重启容器 docker restart web
docker kill 强制停止容器 docker kill web
docker pause 暂停容器 docker pause web
docker unpause 恢复容器 docker unpause web
docker inspect 查看容器详细信息 docker inspect web
docker logs 查看容器日志 docker logs -f --tail 100 web
docker top 查看容器内进程 docker top web
docker stats 查看容器资源使用 docker stats web
docker cp 容器与宿主机复制文件 docker cp web:/etc/nginx/nginx.conf ./(容器→宿主机) docker cp ./index.html web:/usr/share/nginx/html/(宿主机→容器)
docker rm 删除容器 docker rm -f web(强制删除)
docker container prune 清理所有已停止的容器 docker container prune

1.3.3 容器交互

命令 说明 示例
docker exec -it 进入运行中的容器(推荐) docker exec -it web bash docker exec -it web sh(alpine 等轻量镜像)
docker exec -it -u root 以 root 身份进入容器 docker exec -it -u root web bash
docker exec 在容器中执行单条命令 docker exec web cat /etc/nginx/nginx.conf
docker exec -e 设置环境变量并执行命令 docker exec -e MY_VAR=hello web env
docker run -it --rm 交互模式启动临时容器(退出自动删除) docker run -it --rm ubuntu bash
docker attach 连接容器主进程 docker attach web(Ctrl+P Ctrl+Q 分离)
docker logs -f 持续查看容器日志 docker logs -f --tail 50 web

exec vs attach exec 在容器内启动新进程,退出不影响容器运行;attach 连接到容器主进程(PID 1),退出可能导致容器停止。推荐使用 exec

1.3.4 资源限制

Docker 默认不对容器做资源限制,容器可消耗宿主机全部可用资源。在生产环境中,必须通过以下参数限制资源,防止单个容器耗尽资源导致宿主机或其他容器不可用。

参数 说明 示例
--memory / -m 容器可使用的最大物理内存。超出限制时容器内进程会被 OOM Killer 杀掉。支持单位:bkmg docker run -d --memory 512m nginx
--memory-swap 内存 + swap 总上限(须 ≥ --memory)。swap 是磁盘上的虚拟内存空间,物理内存不足时系统将部分不活跃数据暂存到磁盘,速度远慢于内存但可防止进程被 OOM 杀掉。设为与 --memory 相同值则禁用 swap docker run -d --memory 512m --memory-swap 1g nginx(物理内存 512m + swap 512m)
--cpus 容器最多可使用的 CPU 核数。1.5 表示最多使用 1.5 个核心的计算能力 docker run -d --cpus 1.5 nginx
--cpu-shares CPU 时间片的相对权重(默认 1024)。仅在 CPU 资源争抢时生效:权重 512 的容器获得的 CPU 时间是 1024 的一半。CPU 空闲时不受此限制 docker run -d --cpu-shares 512 nginx
--pids-limit 容器内最大进程数,防止 fork 炸弹(恶意或意外无限创建进程)耗尽系统进程表 docker run -d --pids-limit 100 nginx

组合使用示例:

bash 复制代码
docker run -d --name web \
    --memory 512m \
    --cpus 1.5 \
    --pids-limit 100 \
    -p 8080:80 \
    nginx

运行时更新资源限制(无需重启容器):

bash 复制代码
docker update --memory 1g --cpus 2 web

2. Docker 镜像与容器深入

2.1 镜像原理

2.1.1 镜像分层结构

Docker 镜像由多个**只读层(Layer)**叠加而成,每层对应 Dockerfile 中的一条指令。层之间共享复用,极大节省磁盘空间和传输时间。

graph TB subgraph 镜像分层示意 direction TB W[可写层 Container Layer] L4[Layer 4: CMD / EXPOSE] L3[Layer 3: COPY 应用代码] L2[Layer 2: RUN npm install] L1[Layer 1: FROM node:18-alpine] W -->|写时复制 CoW| L4 L4 --> L3 --> L2 --> L1 end style W fill:#e74c3c,color:#fff style L4 fill:#0db7ed,color:#fff style L3 fill:#1a9bd7,color:#fff style L2 fill:#2980b9,color:#fff style L1 fill:#3867a8,color:#fff

核心机制:

概念 说明
联合文件系统(UnionFS) 将多个只读层合并为一个统一的文件系统视图,容器看到的是完整的目录结构
写时复制(Copy-on-Write) 容器运行时在最上层添加可写层;修改文件时先从只读层复制到可写层再修改,不影响原始镜像
层缓存 构建镜像时,未变化的层直接使用缓存,只重建变化的层及其之后的层
内容寻址 每层通过 SHA256 哈希标识,相同内容的层在不同镜像间共享
bash 复制代码
# 查看镜像分层信息
docker history nginx:latest

# 查看镜像详细元数据(含每层 diff ID)
docker inspect nginx:latest

# 查看镜像实际磁盘占用(含共享层)
docker system df -v
2.1.2 镜像标签

标签(Tag)用于标识镜像的不同版本,同一镜像可以有多个标签。

ini 复制代码
镜像全名格式:[仓库地址/]命名空间/镜像名:标签

示例:
nginx:latest          # latest 是默认标签,指向最新版本
nginx:1.25-alpine     # 指定版本 + 变体
node:18-slim          # slim 变体,精简版
python:3.12-bookworm  # 基于 Debian Bookworm

常见标签约定:

标签格式 说明
latest 最新版本(不建议生产使用,内容可能随时变化)
1.25 / 18 主版本号,会随小版本更新
1.25.3 精确版本号(生产推荐)
alpine 基于 Alpine Linux,体积极小(约 5MB)
slim 精简版 Debian,移除了不常用的包
bookworm / bullseye 基于特定 Debian 版本代号
bash 复制代码
# 给镜像打标签
docker tag nginx:latest myrepo/nginx:v1.0
docker tag nginx:latest myrepo/nginx:production

# 查看本地所有标签
docker images nginx

# 删除特定标签(不删除镜像本身,除非是最后一个标签)
docker rmi myrepo/nginx:v1.0
2.1.3 镜像仓库
graph LR DEV[开发者] -->|docker build| LOCAL[本地镜像] LOCAL -->|docker tag + push| REG[远程仓库] REG -->|docker pull| SERVER[服务器] subgraph 仓库类型 PUB[公共仓库
Docker Hub / 阿里云] PRI[私有仓库
Harbor / Registry] end

常用公共仓库:

仓库 地址 说明
Docker Hub hub.docker.com 全球最大,官方镜像仓库
阿里云 ACR cr.console.aliyun.com 国内访问快,免费个人版
GitHub GHCR ghcr.io 与 GitHub 深度集成
腾讯云 TCR cloud.tencent.com/product/tcr 腾讯云容器镜像服务
bash 复制代码
# 登录 Docker Hub
docker login

# 登录私有仓库
docker login registry.cn-hangzhou.aliyuncs.com

# 推送镜像(需先 tag 为仓库地址前缀)
docker tag myapp:v1 registry.cn-hangzhou.aliyuncs.com/mynamespace/myapp:v1
docker push registry.cn-hangzhou.aliyuncs.com/mynamespace/myapp:v1

# 从私有仓库拉取
docker pull registry.cn-hangzhou.aliyuncs.com/mynamespace/myapp:v1

# 登出
docker logout registry.cn-hangzhou.aliyuncs.com
2.1.4 镜像导入导出

在无法访问外网或需要离线部署时,可通过导入导出在不同机器间传输镜像。

bash 复制代码
# 导出镜像为 tar 文件
docker save -o nginx.tar nginx:latest

# 导出多个镜像到一个文件
docker save -o images.tar nginx:latest mysql:8.0 redis:7

# 导入镜像
docker load -i nginx.tar

# 从容器导出文件系统(不含镜像元数据,仅文件系统快照)
docker export -o container-fs.tar my-container

# 从文件系统快照导入为新镜像
docker import container-fs.tar myimage:snapshot

save/load vs export/import save/load 操作的是镜像 ,保留完整的分层和元数据,用于镜像迁移;export/import 操作的是容器文件系统,会丢失分层和历史记录,合并为单层镜像。

2.2 容器生命周期

2.2.1 容器状态
stateDiagram-v2 [*] --> Created: docker create / docker run Created --> Running: docker start Running --> Paused: docker pause Paused --> Running: docker unpause Running --> Stopped: docker stop(优雅停止,先 SIGTERM 后 SIGKILL) Running --> Stopped: docker kill(强制停止,直接 SIGKILL) Stopped --> Running: docker start Stopped --> Deleted: docker rm Running --> Deleted: docker rm -f Deleted --> [*]
状态 说明
Created 容器已创建但未启动,分配了文件系统和网络配置
Running 容器主进程(PID 1)正在运行
Paused 容器进程被挂起(SIGSTOP),冻结在内存中不消耗 CPU
Stopped 容器主进程已退出,文件系统和配置仍保留
Deleted 容器被移除,所有资源释放
bash 复制代码
# 查看容器当前状态
docker inspect --format='{{.State.Status}}' my-container

# 查看容器启动时间、退出码等
docker inspect --format='{{json .State}}' my-container | python3 -m json.tool
2.2.2 容器操作

创建与启动:

bash 复制代码
# 创建并启动(最常用)
docker run -d --name web -p 8080:80 nginx

# 仅创建(稍后启动)
docker create --name web -p 8080:80 nginx
docker start web

# 启动时指定重启策略
docker run -d --name web --restart=unless-stopped -p 8080:80 nginx

重启策略:

策略 说明
no 默认值,不自动重启
on-failure[:max-retries] 非正常退出时重启,可限制最大重试次数
always 无论退出码如何都重启,Docker Daemon 启动时也会拉起
unless-stopped 类似 always,但手动 docker stop 后不会被 Daemon 自动拉起

停止与删除:

bash 复制代码
# 优雅停止(先发 SIGTERM,默认等待 10 秒后发 SIGKILL)
docker stop web

# 指定等待时间
docker stop -t 30 web

# 强制停止
docker kill web

# 删除已停止的容器
docker rm web

# 强制删除运行中的容器
docker rm -f web

# 批量删除所有已停止的容器
docker container prune -f
2.2.3 容器日志
bash 复制代码
# 查看全部日志
docker logs web

# 持续输出日志(类似 tail -f)
docker logs -f web

# 查看最近 100 行
docker logs --tail 100 web

# 显示时间戳
docker logs -t web

# 查看指定时间段的日志
docker logs --since 2024-01-01T00:00:00 --until 2024-01-02T00:00:00 web

# 查看最近 30 分钟的日志
docker logs --since 30m web

日志驱动:

Docker 支持多种日志驱动,默认使用 json-file,日志存储在宿主机的 /var/lib/docker/containers/<container-id>/ 目录下。

bash 复制代码
# 查看容器使用的日志驱动
docker inspect --format='{{.HostConfig.LogConfig.Type}}' web

# 启动时指定日志驱动和配置(限制单个日志文件最大 10MB,最多保留 3 个文件)
docker run -d --name web \
    --log-driver json-file \
    --log-opt max-size=10m \
    --log-opt max-file=3 \
    nginx
2.2.4 资源监控
bash 复制代码
# 实时查看容器资源使用(CPU、内存、网络 I/O、磁盘 I/O)
docker stats

# 查看指定容器
docker stats web

# 仅输出一次(非实时,适合脚本采集)
docker stats --no-stream

# 自定义输出格式
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

# 查看容器内进程
docker top web

# 查看容器文件系统变更(相对于镜像)
docker diff web
# A = 新增,C = 修改,D = 删除

2.3 容器与宿主机交互

2.3.1 端口映射

将宿主机端口映射到容器端口,使外部可以访问容器内的服务。

graph LR USER[外部用户] -->|访问 宿主机:8080| HOST[宿主机] HOST -->|转发到 容器:80| CONTAINER[容器 nginx]
bash 复制代码
# 映射指定端口
docker run -d -p 8080:80 nginx
# 宿主机 8080 → 容器 80

# 映射多个端口
docker run -d -p 8080:80 -p 8443:443 nginx

# 映射到指定 IP(仅本机可访问)
docker run -d -p 127.0.0.1:8080:80 nginx

# 随机映射端口(宿主机自动分配空闲端口)
docker run -d -P nginx

# 查看端口映射关系
docker port web
2.3.2 文件拷贝
bash 复制代码
# 容器 → 宿主机
docker cp web:/etc/nginx/nginx.conf ./nginx.conf

# 宿主机 → 容器
docker cp ./index.html web:/usr/share/nginx/html/index.html

# 拷贝整个目录
docker cp web:/var/log/nginx ./nginx-logs
docker cp ./config/ web:/app/config/

注意: docker cp 不支持容器间直接拷贝,需以宿主机为中转。

2.3.3 进入容器
bash 复制代码
# 推荐方式:exec 启动新进程
docker exec -it web bash        # Bash shell
docker exec -it web sh          # Alpine 等轻量镜像用 sh
docker exec -it -u root web bash  # 以 root 身份进入

# 执行单条命令(不进入交互模式)
docker exec web cat /etc/nginx/nginx.conf
docker exec web ls -la /app

# 传递环境变量
docker exec -e DEBUG=true web node /app/script.js

# attach 方式(连接主进程,不推荐)
docker attach web
# Ctrl+P Ctrl+Q 可安全分离,不停止容器
# 直接 Ctrl+C 会停止容器主进程
2.3.4 容器网络基础

每个容器默认分配独立的网络命名空间和 IP 地址。

bash 复制代码
# 查看容器 IP 地址
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web

# 查看容器网络详情
docker inspect web | grep -A 20 "Networks"

# 从宿主机 ping 容器(需在同一网络)
ping $(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web)

# 查看容器 DNS 配置
docker exec web cat /etc/resolv.conf

# 查看容器 hosts 文件
docker exec web cat /etc/hosts

3. Docker 网络与数据卷

3.1 Docker 网络

3.1.1 网络模式

Docker 提供多种网络模式,适用于不同场景。

graph TB subgraph Bridge模式[bridge 默认模式] BR[docker0 网桥
172.17.0.1] C1B[容器1
172.17.0.2] C2B[容器2
172.17.0.3] BR --- C1B BR --- C2B end subgraph Host模式[host 模式] HN[宿主机网络栈] C1H[容器
共享宿主机 IP 和端口] HN --- C1H end subgraph None模式[none 模式] C1N[容器
无网络,仅 loopback] end
网络模式 说明 使用场景
bridge 默认模式,容器连接到 docker0 虚拟网桥,通过 NAT 访问外网 大多数单机场景
host 容器直接使用宿主机网络栈,无网络隔离,性能最好 对网络性能要求极高的场景
none 容器无网络接口(仅 loopback),完全隔离 安全敏感的离线计算任务
container 与指定容器共享网络命名空间(IP 和端口) Sidecar 模式,如日志收集器
bash 复制代码
# 默认 bridge 模式
docker run -d --name web nginx

# host 模式
docker run -d --network host --name web nginx

# none 模式
docker run -d --network none --name isolated-app myapp

# 与另一个容器共享网络
docker run -d --name app1 nginx
docker run -d --network container:app1 --name app2 busybox
3.1.2 自定义网络

自定义网络提供比默认 bridge 更好的隔离性和 DNS 自动解析(容器间可通过容器名互相访问)。

bash 复制代码
# 创建自定义 bridge 网络
docker network create my-network

# 指定子网和网关
docker network create --subnet 172.20.0.0/16 --gateway 172.20.0.1 my-network

# 在自定义网络中启动容器
docker run -d --name web --network my-network nginx
docker run -d --name api --network my-network node:18

# 容器间通过名称互访(自定义网络自动提供 DNS)
docker exec api ping web    # ✅ 可直接用容器名
docker exec api curl http://web:80  # ✅ HTTP 访问

# 查看网络列表
docker network ls

# 查看网络详情(含已连接的容器)
docker network inspect my-network

# 删除网络
docker network rm my-network

# 清理所有未使用的网络
docker network prune
3.1.3 容器互联
graph TB subgraph 自定义网络 my-network WEB[web 容器
nginx
172.20.0.2] API[api 容器
node-app
172.20.0.3] DB[db 容器
mysql
172.20.0.4] WEB -->|curl http://api:3000| API API -->|mysql -h db| DB end USER[外部用户] -->|宿主机:8080| WEB

推荐方式:使用自定义网络

bash 复制代码
# 创建应用网络
docker network create app-network

# 启动数据库
docker run -d --name db \
    --network app-network \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -e MYSQL_DATABASE=mydb \
    mysql:8.0

# 启动后端 API(通过容器名 db 访问数据库)
docker run -d --name api \
    --network app-network \
    -e DB_HOST=db \
    -e DB_PORT=3306 \
    -p 3000:3000 \
    my-api-app

# 启动前端(通过容器名 api 访问后端)
docker run -d --name web \
    --network app-network \
    -p 8080:80 \
    my-web-app

运行中的容器加入 / 断开网络:

bash 复制代码
# 将已运行的容器连接到网络
docker network connect app-network existing-container

# 断开网络
docker network disconnect app-network existing-container
3.1.4 网络驱动
驱动 说明 适用场景
bridge 单机桥接网络,容器通过虚拟网桥通信 单机开发和测试
host 移除容器网络隔离,直接使用宿主机网络 高性能网络需求
overlay 跨主机网络,多个 Docker 主机上的容器互通 Docker Swarm / 集群
macvlan 为容器分配真实 MAC 地址,使其在物理网络中可见 需要容器直连物理网络
none 禁用网络 安全隔离场景
ipvlan 类似 macvlan,但共享宿主机 MAC 地址 对 MAC 地址有限制的环境
bash 复制代码
# 创建 overlay 网络(需先初始化 Swarm)
docker network create --driver overlay my-overlay

# 创建 macvlan 网络
docker network create --driver macvlan \
    --subnet 192.168.1.0/24 \
    --gateway 192.168.1.1 \
    -o parent=eth0 \
    my-macvlan

3.2 Docker 数据卷

3.2.1 数据卷概念

容器的文件系统是临时的,容器删除后数据会丢失。**数据卷(Volume)**是 Docker 管理的持久化存储机制,独立于容器生命周期。

graph LR subgraph 容器 APP[应用进程] RW["可写层
容器删除即丢失"] VOL["/data
挂载点"] end subgraph 宿主机 DISK["Docker 管理的卷
/var/lib/docker/volumes/"] end APP --> RW APP --> VOL VOL ---|数据卷| DISK

数据卷 vs 绑定挂载:

特性 数据卷(Volume) 绑定挂载(Bind Mount)
管理方式 Docker 管理 用户指定宿主机路径
存储位置 /var/lib/docker/volumes/ 宿主机任意路径
可移植性 高,不依赖宿主机目录结构 低,依赖宿主机具体路径
权限控制 Docker 自动处理 需手动处理权限
性能 最优(Linux 原生) 依赖宿主机文件系统
备份迁移 docker volume 命令管理 需手动操作文件
推荐场景 数据库存储、持久化数据 开发环境代码同步、配置文件
3.2.2 数据卷管理
bash 复制代码
# 创建数据卷
docker volume create my-data

# 查看所有数据卷
docker volume ls

# 查看数据卷详情(存储路径、创建时间等)
docker volume inspect my-data

# 删除数据卷
docker volume rm my-data

# 清理所有未被容器使用的数据卷
docker volume prune -f
3.2.3 挂载数据卷

使用 -v--mount 参数挂载数据卷到容器中。

bash 复制代码
# -v 简写语法:卷名:容器路径[:选项]
docker run -d --name db \
    -v mysql-data:/var/lib/mysql \
    mysql:8.0

# --mount 详细语法(推荐,语义更清晰)
docker run -d --name db \
    --mount source=mysql-data,target=/var/lib/mysql \
    mysql:8.0

# 只读挂载
docker run -d --name web \
    -v nginx-conf:/etc/nginx/conf.d:ro \
    nginx

# 卷不存在时自动创建
docker run -d -v auto-created-vol:/data busybox

参数说明: source 是数据卷的名称(即 docker volume create 创建的卷名);target 是容器内的挂载路径,可以是任意路径,但通常需要指定为应用实际存储数据的目录 才能实现持久化。例如 MySQL 的数据目录是 /var/lib/mysql,Nginx 日志目录是 /var/log/nginx,这些路径由镜像内的应用决定,可通过镜像文档或 docker inspect 查看。

3.2.4 绑定挂载

将宿主机目录直接挂载到容器,适合开发环境中实时同步代码。

bash 复制代码
# 绑定挂载宿主机目录
docker run -d --name web \
    -v /home/user/website:/usr/share/nginx/html \
    -p 8080:80 \
    nginx

# --mount 语法
docker run -d --name web \
    --mount type=bind,source=/home/user/website,target=/usr/share/nginx/html \
    -p 8080:80 \
    nginx

# 只读绑定挂载(容器内无法修改)
docker run -d --name web \
    -v /home/user/nginx.conf:/etc/nginx/nginx.conf:ro \
    nginx

# 开发环境:实时同步代码
docker run -d --name dev-app \
    -v $(pwd)/src:/app/src \
    -p 3000:3000 \
    node:18 npm run dev

路径规则: -v 参数中,以 /./ 开头的为绑定挂载(宿主机路径),否则为命名数据卷。

3.2.5 数据卷容器

数据卷容器是一个专门提供数据卷给其他容器共享的容器,适用于多个容器间共享数据。

bash 复制代码
# 创建数据卷容器(不需要运行)
docker create --name data-store \
    -v shared-data:/data \
    busybox

# 其他容器通过 --volumes-from 共享数据卷
docker run -d --name app1 \
    --volumes-from data-store \
    my-app

docker run -d --name app2 \
    --volumes-from data-store \
    my-app

# app1 和 app2 都可以读写 /data 目录,数据实时共享

3.3 数据持久化实战

3.3.1 MySQL 数据持久化
bash 复制代码
# 创建专用数据卷
docker volume create mysql-data
docker volume create mysql-conf

# 启动 MySQL 并挂载数据卷
docker run -d --name mysql \
    -v mysql-data:/var/lib/mysql \
    -v mysql-conf:/etc/mysql/conf.d \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -e MYSQL_DATABASE=mydb \
    -e MYSQL_USER=app \
    -e MYSQL_PASSWORD=app123 \
    -p 3306:3306 \
    mysql:8.0

# 验证持久化:删除容器后数据不丢失
docker rm -f mysql

# 使用相同的数据卷重新创建容器,数据完整保留
docker run -d --name mysql \
    -v mysql-data:/var/lib/mysql \
    -v mysql-conf:/etc/mysql/conf.d \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -p 3306:3306 \
    mysql:8.0
3.3.2 Nginx 配置持久化
bash 复制代码
# 先启动临时容器,拷贝默认配置到宿主机
docker run -d --name tmp-nginx nginx
docker cp tmp-nginx:/etc/nginx/nginx.conf ./nginx.conf
docker cp tmp-nginx:/etc/nginx/conf.d ./conf.d
docker cp tmp-nginx:/usr/share/nginx/html ./html
docker rm -f tmp-nginx

# 使用绑定挂载启动 Nginx,方便修改配置
docker run -d --name nginx \
    -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
    -v $(pwd)/conf.d:/etc/nginx/conf.d:ro \
    -v $(pwd)/html:/usr/share/nginx/html \
    -v nginx-logs:/var/log/nginx \
    -p 80:80 -p 443:443 \
    nginx

# 修改配置后重载(无需重启容器)
docker exec nginx nginx -s reload
3.3.3 数据备份与恢复

备份数据卷:

bash 复制代码
# 使用临时容器将数据卷内容打包到宿主机
docker run --rm \
    -v mysql-data:/source:ro \
    -v $(pwd):/backup \
    busybox tar czf /backup/mysql-backup-$(date +%Y%m%d).tar.gz -C /source .

# 说明:
# -v mysql-data:/source:ro  将数据卷只读挂载到临时容器 /source
# -v $(pwd):/backup         将宿主机当前目录挂载到 /backup
# tar czf ...               将 /source 内容压缩到 /backup

恢复数据卷:

bash 复制代码
# 创建新数据卷
docker volume create mysql-data-restored

# 使用临时容器将备份文件解压到新数据卷
docker run --rm \
    -v mysql-data-restored:/target \
    -v $(pwd):/backup:ro \
    busybox tar xzf /backup/mysql-backup-20240101.tar.gz -C /target

# 使用恢复后的数据卷启动容器
docker run -d --name mysql-restored \
    -v mysql-data-restored:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -p 3307:3306 \
    mysql:8.0

定时备份脚本示例:

bash 复制代码
#!/bin/bash
# backup-volumes.sh
BACKUP_DIR="/home/user/backups"
DATE=$(date +%Y%m%d_%H%M%S)

# 备份 MySQL 数据
docker run --rm \
    -v mysql-data:/source:ro \
    -v $BACKUP_DIR:/backup \
    busybox tar czf /backup/mysql-$DATE.tar.gz -C /source .

# 保留最近 7 天的备份
find $BACKUP_DIR -name "mysql-*.tar.gz" -mtime +7 -delete

echo "[$DATE] Backup completed."

4. Dockerfile 最佳实践

4.1 Dockerfile 基础

4.1.1 Dockerfile 概念

Dockerfile 是一个文本文件,包含一系列指令(Instruction),用于自动化构建 Docker 镜像。每条指令描述镜像构建的一个步骤,Docker 引擎按顺序执行这些指令,最终生成可运行的镜像。

核心理念:基础设施即代码(Infrastructure as Code)

graph LR A[Dockerfile] -->|docker build| B[Docker 镜像] B -->|docker run| C[容器实例] A -->|版本管理| D[Git 仓库] style A fill:#0db7ed,color:#fff style B fill:#384d54,color:#fff

Dockerfile 基本结构:

dockerfile 复制代码
# 基础镜像
FROM node:18-alpine

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

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY package*.json ./

# 安装依赖
RUN npm install --production

# 复制源代码
COPY . .

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", "server.js"]

4.1.2 基础指令

指令速查表:

指令 作用 语法 是否产生新层
FROM 指定基础镜像,所有指令在此基础上构建;支持多阶段构建中使用 AS 命名阶段 FROM image:tag [AS name]
WORKDIR 设置后续 RUN/CMD/COPY/ADD 等指令的工作目录,目录不存在时自动创建,支持相对路径叠加 WORKDIR /path
COPY 将宿主机构建上下文中的文件/目录复制到镜像中,支持 --chown 指定归属、通配符匹配 COPY [--chown=user] src dest
ADD 功能同 COPY,额外支持自动解压本地 tar 压缩包和从远程 URL 下载文件(推荐优先用 COPY ADD src dest
RUN 构建阶段 执行 Shell 命令(如安装依赖、编译代码),每条 RUN 产生一个新的镜像层 RUN command
CMD 设置容器启动时 的默认命令和参数,仅最后一条生效;运行时可被 docker run 参数覆盖 CMD ["exec", "arg"]
ENTRYPOINT 设置容器的固定入口命令,与 CMD 配合可实现"命令+默认参数"模式;覆盖需加 --entrypoint ENTRYPOINT ["exec"]
EXPOSE 声明容器运行时监听的网络端口,仅起文档说明作用,不会自动映射;实际映射需 -p 参数 EXPOSE port[/protocol]
LABEL 为镜像添加键值对形式的元数据(如作者、版本、描述),可通过 docker inspect 查看 LABEL key=value
ENV 设置环境变量,构建阶段和容器运行时均可用;运行时可通过 docker run -e 覆盖 ENV key=value
ARG 定义仅在构建阶段 有效的变量,通过 --build-arg 传入;不会持久化到最终镜像中 ARG name[=default]
VOLUME 声明匿名卷挂载点,容器启动时自动创建匿名卷;该指令之后对该目录的 RUN 修改不会保存 VOLUME ["/path"]
USER 切换后续指令和容器运行时的用户身份,提升安全性;切换前需确保用户已创建 USER username
HEALTHCHECK 定义容器健康检查命令,Docker 定期执行并标记容器状态为 healthy/unhealthy HEALTHCHECK CMD command

FROM --- 指定基础镜像

每个 Dockerfile 必须FROM 开头(ARG 除外),指定构建的基础镜像。

dockerfile 复制代码
# 使用官方 Node.js 镜像
FROM node:22-alpine

# 使用精简版 Debian 基础镜像
FROM node:22-slim

# 从零开始构建(用于静态编译的二进制)
FROM scratch
WORKDIR --- 设置工作目录

设置后续指令的工作目录,目录不存在时自动创建。

dockerfile 复制代码
WORKDIR /app

# 支持多次切换
WORKDIR /app/src
WORKDIR ../config   # 相对路径,结果为 /app/config
COPY --- 复制文件

将宿主机文件/目录复制到镜像中。

dockerfile 复制代码
# 复制单个文件
COPY package.json /app/

# 复制多个文件
COPY package.json package-lock.json ./

# 复制目录
COPY src/ /app/src/

# 使用通配符
COPY *.conf /etc/nginx/

# --chown 指定文件归属
COPY --chown=node:node . /app/
ADD --- 复制并解压

功能类似 COPY,但额外支持自动解压压缩包远程 URL 下载

dockerfile 复制代码
# 自动解压 tar 文件到目标目录
ADD app.tar.gz /app/

# 下载远程文件(不推荐,建议用 RUN curl)
ADD https://example.com/file.txt /app/

⚠️ 最佳实践 :除非需要自动解压,否则优先使用 COPY,语义更明确。

RUN --- 执行命令

在构建过程中执行命令,每条 RUN 生成一个新的镜像层。

dockerfile 复制代码
# Shell 形式
RUN apt-get update && apt-get install -y curl

# Exec 形式
RUN ["apt-get", "install", "-y", "vim"]

# 多行书写(推荐)
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        curl \
        wget \
        git \
    && rm -rf /var/lib/apt/lists/*
CMD --- 容器启动命令

指定容器启动时的默认命令,只有最后一条 CMD 生效

dockerfile 复制代码
# Exec 形式(推荐)
CMD ["node", "server.js"]

# Shell 形式
CMD node server.js

# 作为 ENTRYPOINT 的默认参数
CMD ["--help"]
ENTRYPOINT --- 入口点

设置容器启动时执行的主命令,与 CMD 配合使用。

dockerfile 复制代码
# 固定入口
ENTRYPOINT ["node", "server.js"]

# ENTRYPOINT + CMD 组合
ENTRYPOINT ["node"]
CMD ["server.js"]
# 运行时可覆盖 CMD:docker run myimage app.js

CMD 与 ENTRYPOINT 对比:

特性 CMD ENTRYPOINT
用途 默认命令/参数 固定入口命令
可覆盖 docker run 参数直接覆盖 --entrypoint 覆盖
多条指令 仅最后一条生效 仅最后一条生效
组合使用 作为 ENTRYPOINT 的默认参数 接收 CMD 作为参数
EXPOSE --- 声明端口

声明容器运行时监听的端口(仅作文档说明,不会自动映射)。

dockerfile 复制代码
EXPOSE 80
EXPOSE 443
EXPOSE 3000/tcp
EXPOSE 5000/udp
LABEL --- 元数据

为镜像添加键值对形式的元数据。

dockerfile 复制代码
LABEL maintainer="dev@example.com"
LABEL version="1.0"
LABEL description="My application"

# 单行写法
LABEL maintainer="dev@example.com" version="1.0"

4.1.3 构建镜像

基本构建命令
bash 复制代码
# 在 Dockerfile 所在目录构建
docker build -t myapp:1.0 .

# 指定 Dockerfile 路径
docker build -f docker/Dockerfile.prod -t myapp:prod .

# 构建时传入参数
docker build --build-arg NODE_ENV=production -t myapp:prod .

# 不使用缓存
docker build --no-cache -t myapp:1.0 .
构建上下文

docker build 命令末尾的 . 表示构建上下文(Build Context),Docker 会将该目录下所有文件发送给 Docker 引擎。

graph LR A[客户端 CLI] -->|发送构建上下文| B[Docker 引擎] B -->|逐条执行指令| C[生成镜像层] C --> D[最终镜像] style B fill:#0db7ed,color:#fff
bash 复制代码
# 当前目录作为上下文
docker build -t myapp .

# 指定其他目录作为上下文
docker build -t myapp /path/to/context

# 使用 URL 作为上下文
docker build -t myapp https://github.com/user/repo.git
查看构建过程
bash 复制代码
# 查看镜像构建历史
docker history myapp:1.0

# 输出示例
IMAGE          CREATED        CREATED BY                                      SIZE
a1b2c3d4e5f6   2 hours ago   CMD ["node" "server.js"]                         0B
b2c3d4e5f6a1   2 hours ago   EXPOSE map[3000/tcp:{}]                          0B
c3d4e5f6a1b2   2 hours ago   COPY dir:xxx in /app                             5.2MB
d4e5f6a1b2c3   2 hours ago   RUN npm install --production                     45MB
e5f6a1b2c3d4   2 hours ago   COPY file:xxx in /app/package*.json              120kB
f6a1b2c3d4e5   2 hours ago   WORKDIR /app                                     0B

4.1.4 镜像标签

标签用于标识镜像的不同版本,格式为 仓库名:标签

bash 复制代码
# 构建时指定标签
docker build -t myapp:1.0 .
docker build -t myapp:latest .

# 给已有镜像打标签
docker tag myapp:1.0 myapp:stable
docker tag myapp:1.0 registry.example.com/myapp:1.0

# 同时打多个标签
docker build -t myapp:1.0 -t myapp:latest .

标签策略建议:

策略 示例 适用场景
语义化版本 myapp:1.2.3 正式发布
Git 提交哈希 myapp:a1b2c3d CI/CD 自动构建
日期标签 myapp:20240101 定期构建
环境标签 myapp:prod, myapp:dev 多环境部署
latest myapp:latest 默认标签(谨慎使用)

⚠️ 注意latest 标签不代表"最新版本",它只是默认标签名。建议始终使用明确的版本标签。


4.2 Dockerfile 进阶

4.2.1 ENV / ARG --- 变量管理

ENV --- 环境变量

设置容器运行时的环境变量,在构建阶段和容器运行时均可用。

dockerfile 复制代码
# 设置单个变量
ENV NODE_ENV=production

# 设置多个变量
ENV APP_HOME=/app \
    APP_PORT=3000 \
    LOG_LEVEL=info

# 在后续指令中引用
WORKDIR $APP_HOME
EXPOSE $APP_PORT
bash 复制代码
# 运行时覆盖环境变量
docker run -e NODE_ENV=development myapp
docker run --env-file .env myapp
ARG --- 构建参数

定义构建时 使用的变量,仅在 docker build 过程中有效,不会保留到运行时。

dockerfile 复制代码
# 定义构建参数(可设默认值)
ARG NODE_VERSION=18
ARG APP_ENV=production

# 在 FROM 之前使用 ARG
ARG BASE_IMAGE=node:18-alpine
FROM $BASE_IMAGE

# 在构建过程中引用
ARG APP_ENV
RUN echo "Building for: $APP_ENV"
bash 复制代码
# 构建时传入参数
docker build --build-arg NODE_VERSION=20 --build-arg APP_ENV=staging -t myapp .

ENV vs ARG 对比:

特性 ENV ARG
构建时可用
运行时可用
可被 docker run -e 覆盖
可被 --build-arg 覆盖
持久化到镜像

⚠️ 安全提示 :不要用 ARG 传递密码等敏感信息,docker history 可以查看到 ARG 的值。

4.2.2 VOLUME --- 数据卷声明

在 Dockerfile 中声明匿名卷挂载点,容器启动时自动创建匿名卷。

dockerfile 复制代码
# 声明单个卷
VOLUME /data

# 声明多个卷
VOLUME ["/data", "/logs", "/config"]

VOLUME 指令的行为:

dockerfile 复制代码
FROM mysql:8.0

# 声明数据目录为卷
VOLUME /var/lib/mysql

# 注意:VOLUME 之后对该目录的修改不会生效!
RUN echo "test" > /var/lib/mysql/test.txt  # ❌ 无效
bash 复制代码
# 运行时会自动创建匿名卷
docker run -d mysql:8.0
docker volume ls
# DRIVER    VOLUME NAME
# local     a1b2c3d4e5f6...  (自动生成的匿名卷)

# 推荐在运行时显式挂载命名卷
docker run -d -v mysql-data:/var/lib/mysql mysql:8.0

⚠️ 注意VOLUME 指令之后,对该目录的 RUN 操作都不会被持久化到镜像中。

4.2.3 HEALTHCHECK --- 健康检查

定义容器的健康检查机制,Docker 会定期执行检查命令判断容器是否健康。

dockerfile 复制代码
# 基本用法
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1

参数说明:

参数 默认值 说明
--interval 30s 检查间隔时间
--timeout 30s 单次检查超时时间
--start-period 0s 容器启动初始化等待时间
--retries 3 连续失败多少次标记为 unhealthy

不同应用类型的健康检查示例:

dockerfile 复制代码
# Web 应用
HEALTHCHECK --interval=15s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 数据库
HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=5 \
    CMD mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD || exit 1

# Redis
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
    CMD redis-cli ping | grep -q PONG || exit 1

# 无 curl 环境,使用 wget
HEALTHCHECK --interval=15s --timeout=3s \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1

# 禁用健康检查(如果基础镜像已定义)
HEALTHCHECK NONE

查看健康状态:

bash 复制代码
# 查看容器健康状态
docker ps
# STATUS 列会显示 (healthy) 或 (unhealthy)

# 查看详细健康检查日志
docker inspect --format='{{json .State.Health}}' my-container | jq

4.2.4 多阶段构建

多阶段构建允许在一个 Dockerfile 中使用多个 FROM 指令,每个 FROM 开启一个新的构建阶段。最终镜像只包含最后一个阶段的内容,从而大幅减小镜像体积

graph LR subgraph 阶段1-构建 A[完整 SDK/编译工具] --> B[编译产物] end subgraph 阶段2-运行 C[精简运行时] --> D[仅复制产物] end B -->|COPY --from| D style A fill:#e74c3c,color:#fff style C fill:#27ae60,color:#fff
Node.js 前端应用示例
dockerfile 复制代码
# ========== 阶段1:构建 ==========
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ========== 阶段2:运行 ==========
FROM nginx:alpine AS production

# --from=builder 表示从名为 "builder" 的构建阶段(即上方 FROM ... AS builder)中复制文件
# 只取构建产物,不携带源码和 node_modules,大幅缩减最终镜像体积
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

多阶段构建体积对比(Node.js):

应用类型 单阶段镜像 多阶段镜像 说明
纯前端 SPA/SSG ~1.2GB ~25MB 最终只需 nginx + 静态文件,无需 Node 运行时
SSR 应用(如 Vike) ~1.2GB ~200-300MB 仍需 Node 运行时 + 生产依赖,但去掉了 devDependencies 和源码

4.3 Dockerfile 最佳实践

4.3.1 减少镜像层数

每条 RUNCOPYADD 指令都会创建一个新的镜像层。合并指令可以减少层数,缩小镜像体积。

❌ 不推荐 --- 过多层数:

dockerfile 复制代码
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y wget
RUN apt-get install -y git
RUN rm -rf /var/lib/apt/lists/*

✅ 推荐 --- 合并为单层:

dockerfile 复制代码
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        curl \
        wget \
        git \
    && rm -rf /var/lib/apt/lists/*

其他减少层数的技巧:

dockerfile 复制代码
# ❌ 多条 ENV
ENV APP_NAME=myapp
ENV APP_PORT=3000
ENV LOG_LEVEL=info

# ✅ 合并 ENV
ENV APP_NAME=myapp \
    APP_PORT=3000 \
    LOG_LEVEL=info

# ❌ 多条 LABEL
LABEL maintainer="dev@example.com"
LABEL version="1.0"

# ✅ 合并 LABEL
LABEL maintainer="dev@example.com" \
      version="1.0"

4.3.2 利用构建缓存

Docker 按指令顺序构建,如果某层的输入未改变,会直接使用缓存。将变化频率低的指令放在前面,变化频率高的放在后面

❌ 不推荐 --- 源码变动导致依赖重装:

dockerfile 复制代码
FROM node:18-alpine
WORKDIR /app
COPY . .                  # 任何文件改变都会使缓存失效
RUN npm install           # 每次都要重新安装依赖
CMD ["node", "server.js"]

✅ 推荐 --- 分离依赖安装与源码复制:

dockerfile 复制代码
FROM node:22-alpine
WORKDIR /app

# 第1步:仅复制依赖描述文件(变化少)
COPY package.json ./

# 第2步:安装依赖(仅依赖文件变化时重新执行)
# 注意:跨平台(如 Windows 开发 → Linux 容器)时不要复制 package-lock.json
# 否则 npm ci 会按 lock 文件安装,导致缺少 Linux 平台的可选依赖
RUN npm install --production

# 第3步:复制源代码(变化频繁,放最后)
COPY . .

CMD ["node", "server.js"]
graph TB A["COPY package.json(缓存命中 ✅)"] --> B["RUN npm install(缓存命中 ✅)"] B --> C["COPY . .(代码改变,缓存失效 ❌)"] C --> D["CMD ...(重新执行)"] style A fill:#27ae60,color:#fff style B fill:#27ae60,color:#fff style C fill:#e74c3c,color:#fff

4.3.3 .dockerignore

.dockerignore 文件用于排除不需要发送到构建上下文的文件,减小上下文体积,加快构建速度,避免敏感信息泄露

bash 复制代码
# .dockerignore

# 版本控制
.git
.gitignore

# 依赖目录
node_modules
vendor
__pycache__

# 构建产物
dist
build
*.jar
target

# IDE 与编辑器
.vscode
.idea
*.swp
*.swo

# 环境与配置
.env
.env.local
.env.*.local
*.pem
*.key

# Docker 相关
Dockerfile*
docker-compose*.yml
.dockerignore

# 文档与测试
README.md
docs/
test/
tests/
coverage/

# 系统文件
.DS_Store
Thumbs.db

# 日志
*.log
logs/

效果对比:

bash 复制代码
# 无 .dockerignore
Sending build context to Docker daemon  450MB  ← 包含 node_modules 等

# 有 .dockerignore
Sending build context to Docker daemon  2.5MB  ← 仅必要文件

4.3.4 选择基础镜像

选择合适的基础镜像对镜像体积和安全性影响巨大。

常见基础镜像对比:

基础镜像 体积 包管理器 适用场景
ubuntu:22.04 ~77MB apt 需要完整 Linux 环境
debian:bookworm-slim ~74MB apt 通用场景(精简版)
alpine:3.19 ~7MB apk 追求极致精简
distroless ~2-20MB 仅运行时,安全优先
scratch 0MB 静态编译的二进制

Node.js 镜像变体:

bash 复制代码
node:18          # ~1GB,包含完整开发工具
node:18-slim     # ~200MB,精简版 Debian
node:18-alpine   # ~170MB,基于 Alpine Linux

选择建议:

dockerfile 复制代码
# 开发/调试阶段 --- 功能完整
FROM node:18

# 生产环境 --- 推荐 slim
FROM node:18-slim

# 极致精简 --- 使用 Alpine(注意 musl libc 兼容性)
FROM node:18-alpine

# Go/Rust 等静态编译 --- 使用 scratch
FROM scratch

4.3.5 使用非 root 用户

默认情况下容器以 root 用户运行,这是一个安全隐患。应创建并切换到普通用户。

dockerfile 复制代码
FROM node:18-alpine

# 创建应用用户和组
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

# 复制文件并指定归属
COPY --chown=appuser:appgroup . .

# 切换到非 root 用户
USER appuser

EXPOSE 3000
CMD ["node", "server.js"]

验证用户身份:

bash 复制代码
# 进入容器查看当前用户
docker exec -it my-container whoami
# appuser

# 查看进程运行用户
docker exec -it my-container ps aux

⚠️ 注意USER 指令之前的 RUN 命令仍以 root 执行,因此安装软件包等操作应在 USER 之前完成。


4.4 镜像优化

4.4.1 镜像体积优化

清理不必要的文件
dockerfile 复制代码
# ❌ 残留缓存
RUN apt-get update && apt-get install -y curl

# ✅ 同一层中清理
RUN apt-get update \
    && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

# ✅ npm 清理缓存
RUN npm install --production && npm cache clean --force

# ✅ apk 不保留缓存
RUN apk add --no-cache curl wget
使用 .dockerignore 排除无关文件

参见 [4.3.3 .dockerignore](#4.3.3 .dockerignore "#433-dockerignore")。

多阶段构建

参见 [4.2.4 多阶段构建](#4.2.4 多阶段构建 "#424-%E5%A4%9A%E9%98%B6%E6%AE%B5%E6%9E%84%E5%BB%BA")。

压缩与合并层
bash 复制代码
# 使用 --squash 合并层(实验性功能)
docker build --squash -t myapp:squashed .

# 导出再导入压缩镜像
docker export $(docker create myapp:1.0) | docker import - myapp:compressed

优化效果示例:

优化手段 原始体积 优化后 减少
slim 替代完整镜像 1.0GB 200MB 80%
alpine 替代 slim 200MB 170MB 15%
多阶段构建 1.2GB 25MB 98%
清理包管理缓存 350MB 280MB 20%
.dockerignore 构建上下文 450MB 2.5MB 99%

4.4.2 镜像安全扫描

定期扫描镜像中的已知漏洞,是生产环境的必要步骤。

Docker Scout(官方工具)
bash 复制代码
# 扫描本地镜像
docker scout cves myapp:1.0

# 快速概览
docker scout quickview myapp:1.0

# 查看改进建议
docker scout recommendations myapp:1.0
Trivy(开源工具,推荐)
bash 复制代码
# 安装 Trivy
docker run --rm aquasec/trivy --version

# 扫描镜像漏洞
docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image myapp:1.0

# 仅显示高危和严重漏洞
docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image --severity HIGH,CRITICAL myapp:1.0

# 输出 JSON 格式(适合 CI/CD)
docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image -f json -o results.json myapp:1.0
CI/CD 集成示例
yaml 复制代码
# GitHub Actions 示例
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:${{ github.sha }}
    format: 'table'
    exit-code: '1'                # 发现漏洞时失败
    severity: 'CRITICAL,HIGH'

安全最佳实践清单:

  • ✅ 使用官方/可信基础镜像
  • ✅ 固定镜像版本标签(不用 latest
  • ✅ 定期更新基础镜像
  • ✅ 使用非 root 用户运行
  • ✅ 不在镜像中存储敏感信息
  • ✅ 使用多阶段构建减少攻击面
  • ✅ 在 CI/CD 中集成安全扫描

4.4.3 镜像分层优化

理解 Docker 镜像的分层机制,有助于写出高效的 Dockerfile。

镜像层原理
graph TB subgraph 镜像分层 L1["Layer 1: FROM alpine(基础镜像层)"] L2["Layer 2: RUN apk add...(安装依赖)"] L3["Layer 3: COPY package.json(依赖描述)"] L4["Layer 4: RUN npm install(安装依赖)"] L5["Layer 5: COPY . .(应用代码)"] L1 --> L2 --> L3 --> L4 --> L5 end style L1 fill:#384d54,color:#fff style L5 fill:#e74c3c,color:#fff
  • 每一层都是只读的,通过联合文件系统(UnionFS)叠加
  • 容器运行时在最顶层添加一个可写层
  • 层是可共享的:多个镜像可复用相同的底层
查看镜像层
bash 复制代码
# 查看各层大小
docker history myapp:1.0

# 使用 dive 工具深入分析(推荐)
docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive myapp:1.0

dive 可以交互式地浏览每一层的文件变化,发现不必要的大文件。

分层优化策略

1. 将变化频率相同的操作放在同一层:

dockerfile 复制代码
# ✅ 系统依赖(变化很少)--- 放在前面
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# ✅ 应用依赖(偶尔变化)--- 放在中间
COPY package.json package-lock.json ./
RUN npm ci --production

# ✅ 应用代码(经常变化)--- 放在最后
COPY . .

2. 避免在高层删除低层文件:

dockerfile 复制代码
# ❌ 文件仍保留在 Layer 2 中,镜像体积不会减小
COPY large-file.tar.gz /tmp/       # Layer 2: +500MB
RUN tar xzf /tmp/large-file.tar.gz && rm /tmp/large-file.tar.gz  # Layer 3

# ✅ 在同一层完成下载、解压、清理
RUN curl -O https://example.com/large-file.tar.gz \
    && tar xzf large-file.tar.gz \
    && rm large-file.tar.gz

3. 合理利用层共享:

dockerfile 复制代码
# 团队多个项目使用相同基础层
# Dockerfile.base
FROM node:22-alpine
RUN apk add --no-cache make g++

# Dockerfile.app1 / Dockerfile.app2
FROM myteam/base:1.0    # 共享基础层
COPY . /app

完整优化示例对比:

dockerfile 复制代码
# ========== 优化前 ==========
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/server.js"]
# 镜像体积:~1.4GB

# ========== 优化后 ==========
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder --chown=app:app /app/dist ./dist
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --from=builder --chown=app:app /app/package.json ./
USER app
EXPOSE 3000
HEALTHCHECK --interval=15s --timeout=3s \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
# 镜像体积:~180MB

5. Docker Compose

5.1 Docker Compose 基础

5.1.1 Compose 概念

Docker Compose 是一个用于定义和运行多容器应用的工具。通过一个 YAML 文件描述所有服务、网络和数据卷,一条命令即可启动整个应用栈。

核心价值:

  • 一个文件定义整个应用架构
  • 一条命令启动/停止所有服务
  • 服务间自动网络互通(通过服务名访问)
  • 环境一致性(开发、测试、生产)
graph TB A[docker-compose.yml] -->|docker compose up| B[Docker Compose] B --> C[Service: app] B --> D[Service: nginx] B --> E[Service: db] B --> F[Network: 自动创建] B --> G[Volume: 数据持久化] style A fill:#0db7ed,color:#fff style B fill:#384d54,color:#fff

5.1.2 docker-compose.yml

docker-compose.yml 是 Compose 的核心配置文件,使用 YAML 格式描述应用的服务、网络和数据卷。

基本结构:

yaml 复制代码
# 服务定义
services:
  # 服务名(自定义,同时作为容器间的 DNS 名称)
  web:
    image: nginx:alpine          # 使用镜像
    ports:
      - "80:80"                  # 端口映射 宿主机:容器

  app:
    build: .                     # 从 Dockerfile 构建
    ports:
      - "3000:3000"
    environment:                 # 环境变量
      - NODE_ENV=production

  db:
    image: mysql:8.0
    volumes:                     # 数据卷挂载
      - db-data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=123456

# 数据卷定义
volumes:
  db-data:

# 网络定义(可选,Compose 会自动创建默认网络)
networks:
  app-net:

常用配置项速查:

配置项 作用 示例
image 指定使用的 Docker 镜像,Compose 直接拉取该镜像创建容器,不需要本地 Dockerfile image: nginx:alpine
build 从本地 Dockerfile 构建镜像;可指定构建上下文目录和 Dockerfile 文件名 build: .build: { context: ., dockerfile: Dockerfile }
ports 将容器端口映射到宿主机,格式 宿主机端口:容器端口,外部可通过宿主机端口访问服务 ports: ["80:80", "443:443"]
expose 仅在 Compose 内部网络暴露端口,供其他服务访问,不映射到宿主机,外部无法直接访问 expose: ["3000"]
environment 直接定义容器的环境变量,容器内应用可通过 process.env 读取 environment: [NODE_ENV=production]
env_file 从外部 .env 文件批量加载环境变量,适合管理大量配置项,避免在 yml 中硬编码敏感信息 env_file: .env
volumes 挂载数据卷或宿主机目录到容器内,实现数据持久化;容器删除后数据仍保留 volumes: [./data:/app/data]
depends_on 声明服务间的启动依赖关系,确保被依赖的服务先启动;可配合 condition 等待服务健康 depends_on: [db, redis]
restart 定义容器异常退出时的重启策略:no(不重启)、always(总是重启)、unless-stopped(除手动停止外自动重启) restart: unless-stopped
networks 将服务加入指定网络,同一网络内的服务可通过服务名互相访问;不同网络间相互隔离 networks: [app-net]
deploy 部署相关配置,如副本数(replicas)、资源限制(resources)、更新策略(update_config deploy: { replicas: 3 }
command 覆盖 Dockerfile 中定义的 CMD,指定容器启动时执行的命令 command: ["node", "server.js"]
healthcheck 定义容器的健康检查命令,Docker 定期执行并标记容器状态,可配合 depends_on.condition 实现依赖等待 healthcheck: { test: ["CMD", "curl", "-f", "http://localhost"] }

5.1.3 基本命令

bash 复制代码
# ========== 启动与停止 ==========

# 启动所有服务(后台运行)
docker compose up -d

# 构建并启动(代码有改动时加 --build)
docker compose up -d --build

# 停止所有服务
docker compose stop

# 停止并删除容器、网络
docker compose down

# 停止并删除容器、网络、数据卷(⚠️ 会删除持久化数据)
docker compose down -v

# ========== 查看状态 ==========

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

# 查看服务日志
docker compose logs

# 实时跟踪日志
docker compose logs -f

# 查看指定服务日志
docker compose logs -f app

# ========== 服务管理 ==========

# 重启指定服务
docker compose restart app

# 进入服务容器
docker compose exec app sh

# 在服务中执行一次性命令
docker compose run --rm app node -v

# 水平扩展服务实例
docker compose up -d --scale app=5

# ========== 构建相关 ==========

# 仅构建镜像(不启动)
docker compose build

# 不使用缓存重新构建
docker compose build --no-cache

# 拉取配置中的镜像
docker compose pull

常用命令速查表:

命令 作用
docker compose up -d 后台启动所有服务
docker compose up -d --build 重新构建并启动
docker compose down 停止并清理容器和网络
docker compose ps 查看服务状态
docker compose logs -f 实时查看日志
docker compose exec <服务名> sh 进入容器
docker compose restart <服务名> 重启指定服务
docker compose --scale <服务名>=N 扩展到 N 个实例

5.2 服务编排

5.2.1 多服务应用

一个典型的 Web 应用通常包含多个服务协同工作:

graph LR User[用户] --> Nginx[Nginx 反向代理] Nginx --> App1[App 实例1] Nginx --> App2[App 实例2] App1 --> DB[(MySQL)] App2 --> DB App1 --> Cache[(Redis)] App2 --> Cache style Nginx fill:#009639,color:#fff style DB fill:#4479A1,color:#fff style Cache fill:#DC382D,color:#fff
yaml 复制代码
services:
  # 反向代理
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    restart: unless-stopped

  # 应用服务
  app:
    build: .
    expose:
      - "3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
      - REDIS_HOST=cache
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped

  # 数据库
  db:
    image: mysql:8.0
    volumes:
      - db-data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=123456
      - MYSQL_DATABASE=myapp
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # 缓存
  cache:
    image: redis:7-alpine
    volumes:
      - cache-data:/data
    restart: unless-stopped

volumes:
  db-data:
  cache-data:

5.2.2 服务依赖

depends_on 控制服务启动顺序,支持两种写法:

简单依赖(仅控制启动顺序):

yaml 复制代码
services:
  app:
    depends_on:
      - db        # db 先启动,但不等它就绪
      - cache

条件依赖(等待服务就绪):

yaml 复制代码
services:
  app:
    depends_on:
      db:
        condition: service_healthy    # 等 db 健康检查通过
      cache:
        condition: service_started    # 等 cache 容器启动即可
条件 说明
service_started 容器启动即满足(默认)
service_healthy 容器健康检查通过(需配置 healthcheck)
service_completed_successfully 容器执行完毕且退出码为 0

⚠️ 注意depends_on 只控制启动顺序 ,不保证服务内的应用已完全就绪。建议配合 healthcheck 使用 condition: service_healthy

5.2.3 环境变量

Compose 支持多种方式注入环境变量:

方式一:直接在 yml 中定义

yaml 复制代码
services:
  app:
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DB_HOST=db

方式二:使用 .env 文件

bash 复制代码
# .env(与 docker-compose.yml 同目录)
NODE_ENV=production
PORT=3000
DB_HOST=db
DB_PASSWORD=secret123
yaml 复制代码
services:
  app:
    env_file:
      - .env

方式三:在 yml 中引用宿主机环境变量

yaml 复制代码
services:
  app:
    environment:
      - NODE_ENV=${NODE_ENV:-production}    # 默认值 production
      - DB_PASSWORD=${DB_PASSWORD}          # 从宿主机环境变量读取

优先级(高 → 低):

  1. docker compose run -e 命令行参数
  2. environment 字段直接定义
  3. env_file 文件
  4. Dockerfile 中的 ENV

5.2.4 网络和数据卷

网络

Compose 会自动为项目创建一个默认网络 ,所有服务自动加入,可通过服务名互相访问。

yaml 复制代码
services:
  app:
    # 可通过 "db" 访问数据库,如 mysql://db:3306
    # 可通过 "cache" 访问 Redis,如 redis://cache:6379
    environment:
      - DB_HOST=db
      - REDIS_HOST=cache

  db:
    image: mysql:8.0

  cache:
    image: redis:7-alpine

自定义网络(隔离不同服务组):

yaml 复制代码
services:
  nginx:
    networks:
      - frontend        # nginx 只在前端网络

  app:
    networks:
      - frontend        # app 同时在前端和后端网络
      - backend

  db:
    networks:
      - backend          # db 只在后端网络(nginx 无法直接访问)

networks:
  frontend:
  backend:
graph TB subgraph frontend 网络 Nginx[nginx] App[app] end subgraph backend 网络 App2[app] DB[(db)] end Nginx --> App App2 --> DB style Nginx fill:#009639,color:#fff style DB fill:#4479A1,color:#fff
数据卷
yaml 复制代码
services:
  db:
    volumes:
      # 命名卷 --- 由 Docker 管理,持久化存储
      - db-data:/var/lib/mysql

      # 绑定挂载 --- 映射宿主机目录
      - ./init-sql:/docker-entrypoint-initdb.d:ro

      # 只读挂载(:ro)
      - ./config/my.cnf:/etc/mysql/conf.d/my.cnf:ro

volumes:
  db-data:              # 声明命名卷
    driver: local       # 默认驱动
挂载类型 语法 适用场景
命名卷 vol-name:/container/path 数据库数据、持久化文件
绑定挂载 ./host/path:/container/path 配置文件、初始化脚本
只读挂载 ./path:/container/path:ro 配置文件(防止容器修改)

5.3 Compose 实战

5.3.1 vike-zyh-test 项目部署

以当前项目为例,使用 Docker Compose 部署 Vike SSR 应用 + Nginx 负载均衡

项目架构:

graph LR User[用户 :80] --> Nginx[Nginx 反向代理] Nginx --> App1[vike-app 实例1 :3000] Nginx --> App2[vike-app 实例2 :3000] Nginx --> App3[vike-app 实例3 :3000] style Nginx fill:#009639,color:#fff style App1 fill:#0db7ed,color:#fff style App2 fill:#0db7ed,color:#fff style App3 fill:#0db7ed,color:#fff

docker-compose.yml:

yaml 复制代码
services:
  # 应用服务(可水平扩展多个实例)
  app:
    build: .
    environment:
      - NODE_ENV=production
      - PORT=3000
    # 不对外暴露端口,由 nginx 统一转发
    expose:
      - "3000"
    restart: unless-stopped
    deploy:
      replicas: 3  # 启动 3 个容器实例

  # Nginx 负载均衡
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    restart: unless-stopped

nginx.conf:

nginx 复制代码
events {
    worker_connections 1024;
}

http {
    # 上游服务组 --- Docker Compose DNS 自动解析所有 app 实例
    upstream app_servers {
        # 默认轮询策略,请求依次分配到每个容器
        server app:3000;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://app_servers;
            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;
        }
    }
}

关键点upstreamserver app:3000app 是 Compose 服务名。Docker 内置 DNS 会将 app 解析为所有 app 容器的 IP,Nginx 自动轮询分发请求。

部署与管理:

bash 复制代码
# 1. 构建并启动
docker compose up -d --build

# 2. 查看服务状态
docker compose ps
# NAME            SERVICE   STATUS    PORTS
# project-app-1   app      running   3000/tcp
# project-app-2   app      running   3000/tcp
# project-app-3   app      running   3000/tcp
# project-nginx-1 nginx    running   0.0.0.0:80->80/tcp

# 3. 访问应用
# 浏览器打开 http://localhost

# 4. 查看日志(观察请求分配到不同实例)
docker compose logs -f app

# 5. 动态扩缩容
docker compose up -d --scale app=5    # 扩展到 5 个实例
docker compose up -d --scale app=2    # 缩减到 2 个实例

# 6. 更新部署(代码改动后)
docker compose up -d --build

# 7. 停止并清理
docker compose down

5.3.2 GitHub Actions + Docker Compose 自动部署

推送代码 → GitHub Actions 构建镜像并推送到本地 Registry → 服务器 Docker Compose 拉取部署。

graph LR A[git push] --> B[GitHub Actions] B --> C[构建镜像] C --> D[推送到本地 Registry :5000] D --> E[SSH 服务器] E --> F["docker compose pull & up"] style B fill:#2088FF,color:#fff style D fill:#0db7ed,color:#fff

前置条件:服务器上启动本地 Registry(本地启动测试的镜像仓库,用于模拟)

bash 复制代码
docker run \
    -d \                          # 后台运行容器
    -p 5000:5000 \                # 将宿主机 5000 端口映射到容器 5000 端口(Registry 默认端口)
    --name registry \             # 容器命名为 registry,方便后续管理
    --restart unless-stopped \    # 异常退出自动重启,手动 stop 除外
    registry:2                    # 使用官方 Registry v2 镜像

步骤一:服务器上的 docker-compose.prod.yml

yaml 复制代码
# /opt/vike-zyh-test/docker-compose.prod.yml
services:
  app:
    image: localhost:5000/vike-zyh-test:latest
    environment:
      - NODE_ENV=production
      - PORT=3000
    expose:
      - "3000"
    restart: unless-stopped
    deploy:
      replicas: 3

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    restart: unless-stopped

步骤二:GitHub Actions 工作流

yaml 复制代码
# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:       # 手动触发(Actions 页面点击 "Run workflow")

env:
  REGISTRY: ${{ secrets.SERVER_HOST }}:5000
  IMAGE_NAME: vike-zyh-test

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # 配置 Docker 允许推送到 HTTP 仓库(本地 Registry 无 HTTPS)
      - name: Configure insecure registry
        run: |
          echo '{ "insecure-registries": ["${{ env.REGISTRY }}"] }' | sudo tee /etc/docker/daemon.json
          sudo systemctl restart docker

      - name: Build image
        run: docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest .

      - name: Push to registry
        run: docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /opt/vike-zyh-test
            docker compose -f docker-compose.prod.yml pull
            docker compose -f docker-compose.prod.yml up -d
            docker image prune -f

步骤三:配置 GitHub Secrets

在仓库 Settings → Secrets and variables → Actions 中添加:

Secret 名称 说明
SERVER_HOST 192.168.1.100 部署服务器的公网/局域网 IP(同时运行 Registry),不是 GitHub 账号
SERVER_USER Administrator 部署服务器的 SSH 登录用户名,不是 GitHub 用户名
SERVER_SSH_KEY 私钥文件内容 用于 SSH 登录服务器的私钥(cat ~/.ssh/id_rsa),不是 GitHub 的 key

本地模拟推送并启动服务 原本正常应该使用action进行推送,但是因为没有测试的镜像仓库,因此本地启动了一个测试镜像仓库,并模拟action中的构建和推送

bash 复制代码
# 模拟:
# 1. 构建镜像
docker build -t localhost:5000/vike-zyh-test:latest .
# 2. 推送到本地 Registry
docker push localhost:5000/vike-zyh-test:latest


# 到服务器上部署:
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d

完结撒花~~~

相关推荐
PD我是你的真爱粉1 小时前
Vue 3 生命周期完全指南:从流程图到最佳实践
前端·vue.js·流程图
耀耀切克闹灬1 小时前
前端签章数据的模板处理
前端
掘金安东尼2 小时前
⏰前端周刊第 454 期(2026年2月16日-2月22日)
前端·javascript·面试
掘金安东尼2 小时前
⏰前端周刊第 453 期(2026年2月9日-2月15日)
前端·javascript·面试
Amumu121382 小时前
CSS进阶导读
前端·css
anyup2 小时前
uniapp开发的鸿蒙应用上架后,竟然月入4000+
前端·vue.js·harmonyos
无尽的沉默2 小时前
使用Thymeleaf配置国际化页面(语言切换)
前端·spring boot
代码老中医2 小时前
接手老项目的一个月,我重构了那个2000行的“祖传”React组件
前端
用户83040713057013 小时前
路由传参刷新丢失问题:三种解决方案与最佳实践
前端