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<br>docker pull<br>docker run<br>docker ps<br>..."]
end
subgraph Host[Docker Host 宿主机]
DAEMON[Docker Daemon<br>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 构建:使用声明式语法定义镜像内容
常用命令:
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 --> [*]
常用命令:
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<br>hub.docker.com]
GH[GitHub Container Registry<br>ghcr.io]
AL[阿里云镜像仓库<br>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
镜像命名规范:
[仓库地址/]命名空间/镜像名:标签
示例:
nginx:latest # 官方镜像,默认 Docker Hub
library/nginx:1.25 # 完整官方路径
registry.cn-hangzhou.aliyuncs.com/myns/myapp:v1.0 # 阿里云私有仓库
常用命令:
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<br>docker CLI 命令行工具]
DAEMON[Docker Daemon<br>dockerd 后台守护进程]
RT[containerd + runc<br>容器运行时]
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:
# 启用 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
# 进入 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 支持(推荐)
# 编辑 WSL 配置文件
sudo tee /etc/wsl.conf <<-'EOF'
[boot]
systemd=true
EOF
然后在 PowerShell 中重启 WSL:
wsl --shutdown
wsl -d Ubuntu
重启后 Docker 会随 systemd 自动启动。
方式二:每次手动启动
# 启动 Docker 服务
sudo service docker start
1.2.3 镜像加速配置
国内访问 Docker Hub 较慢,需要配置镜像加速器。
在 WSL Ubuntu 中配置:
# 创建或编辑 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 验证安装
基本验证
# 查看 Docker 版本
docker --version
# 输出示例:Docker version 27.x.x, build xxxxxxx
# 查看详细版本信息(Client + Server)
docker version
# 查看 Docker 系统信息
docker info
运行测试容器
# 运行 hello-world 测试镜像
docker run hello-world
看到以下输出说明安装成功:
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
验证镜像加速是否生效
# 查看 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/
运行一个实际容器验证
# 运行 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 bashdocker 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 |
execvsattach:exec在容器内启动新进程,退出不影响容器运行;attach连接到容器主进程(PID 1),退出可能导致容器停止。推荐使用exec。
1.3.4 资源限制
Docker 默认不对容器做资源限制,容器可消耗宿主机全部可用资源。在生产环境中,必须通过以下参数限制资源,防止单个容器耗尽资源导致宿主机或其他容器不可用。
| 参数 | 说明 | 示例 |
|---|---|---|
--memory / -m | 容器可使用的最大物理内存。超出限制时容器内进程会被 OOM Killer 杀掉。支持单位:b、k、m、g | 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 |
组合使用示例:
docker run -d --name web \
--memory 512m \
--cpus 1.5 \
--pids-limit 100 \
-p 8080:80 \
nginx
运行时更新资源限制(无需重启容器):
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 哈希标识,相同内容的层在不同镜像间共享 |
# 查看镜像分层信息
docker history nginx:latest
# 查看镜像详细元数据(含每层 diff ID)
docker inspect nginx:latest
# 查看镜像实际磁盘占用(含共享层)
docker system df -v
2.1.2 镜像标签
标签(Tag)用于标识镜像的不同版本,同一镜像可以有多个标签。
镜像全名格式:[仓库地址/]命名空间/镜像名:标签
示例:
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 版本代号 |
# 给镜像打标签
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[公共仓库<br>Docker Hub / 阿里云]
PRI[私有仓库<br>Harbor / Registry]
end
常用公共仓库:
| 仓库 | 地址 | 说明 |
|---|---|---|
| Docker Hub | hub.docker.com | 全球最大,官方镜像仓库 |
| 阿里云 ACR | cr.console.aliyun.com | 国内访问快,免费个人版 |
| GitHub GHCR | ghcr.io | 与 GitHub 深度集成 |
| 腾讯云 TCR | cloud.tencent.com/product/tcr | 腾讯云容器镜像服务 |
# 登录 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 镜像导入导出
在无法访问外网或需要离线部署时,可通过导入导出在不同机器间传输镜像。
# 导出镜像为 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/loadvsexport/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 | 容器被移除,所有资源释放 |
# 查看容器当前状态
docker inspect --format='{{.State.Status}}' my-container
# 查看容器启动时间、退出码等
docker inspect --format='{{json .State}}' my-container | python3 -m json.tool
2.2.2 容器操作
创建与启动:
# 创建并启动(最常用)
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 自动拉起 |
停止与删除:
# 优雅停止(先发 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 容器日志
# 查看全部日志
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>/ 目录下。
# 查看容器使用的日志驱动
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 资源监控
# 实时查看容器资源使用(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]
# 映射指定端口
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 文件拷贝
# 容器 → 宿主机
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 进入容器
# 推荐方式: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 地址。
# 查看容器 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 网桥<br>172.17.0.1]
C1B[容器1<br>172.17.0.2]
C2B[容器2<br>172.17.0.3]
BR --- C1B
BR --- C2B
end
subgraph Host模式[host 模式]
HN[宿主机网络栈]
C1H[容器<br>共享宿主机 IP 和端口]
HN --- C1H
end
subgraph None模式[none 模式]
C1N[容器<br>无网络,仅 loopback]
end
| 网络模式 | 说明 | 使用场景 |
|---|---|---|
| bridge | 默认模式,容器连接到 docker0 虚拟网桥,通过 NAT 访问外网 | 大多数单机场景 |
| host | 容器直接使用宿主机网络栈,无网络隔离,性能最好 | 对网络性能要求极高的场景 |
| none | 容器无网络接口(仅 loopback),完全隔离 | 安全敏感的离线计算任务 |
| container | 与指定容器共享网络命名空间(IP 和端口) | Sidecar 模式,如日志收集器 |
# 默认 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 自动解析(容器间可通过容器名互相访问)。
# 创建自定义 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 容器<br>nginx<br>172.20.0.2]
API[api 容器<br>node-app<br>172.20.0.3]
DB[db 容器<br>mysql<br>172.20.0.4]
WEB -->|curl http://api:3000| API
API -->|mysql -h db| DB
end
USER[外部用户] -->|宿主机:8080| WEB
推荐方式:使用自定义网络
# 创建应用网络
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
运行中的容器加入 / 断开网络:
# 将已运行的容器连接到网络
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 地址有限制的环境 |
# 创建 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["可写层<br>容器删除即丢失"]
VOL["/data<br>挂载点"]
end
subgraph 宿主机
DISK["Docker 管理的卷<br>/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 数据卷管理
# 创建数据卷
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 参数挂载数据卷到容器中。
# -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 绑定挂载
将宿主机目录直接挂载到容器,适合开发环境中实时同步代码。
# 绑定挂载宿主机目录
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 数据卷容器
数据卷容器是一个专门提供数据卷给其他容器共享的容器,适用于多个容器间共享数据。
# 创建数据卷容器(不需要运行)
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 数据持久化
# 创建专用数据卷
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 配置持久化
# 先启动临时容器,拷贝默认配置到宿主机
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 数据备份与恢复
备份数据卷:
# 使用临时容器将数据卷内容打包到宿主机
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
恢复数据卷:
# 创建新数据卷
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
定时备份脚本示例:
#!/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 基本结构:
# 基础镜像
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 除外),指定构建的基础镜像。
# 使用官方 Node.js 镜像
FROM node:22-alpine
# 使用精简版 Debian 基础镜像
FROM node:22-slim
# 从零开始构建(用于静态编译的二进制)
FROM scratch
WORKDIR — 设置工作目录
设置后续指令的工作目录,目录不存在时自动创建。
WORKDIR /app
# 支持多次切换
WORKDIR /app/src
WORKDIR ../config # 相对路径,结果为 /app/config
COPY — 复制文件
将宿主机文件/目录复制到镜像中。
# 复制单个文件
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 下载。
# 自动解压 tar 文件到目标目录
ADD app.tar.gz /app/
# 下载远程文件(不推荐,建议用 RUN curl)
ADD https://example.com/file.txt /app/
⚠️ 最佳实践:除非需要自动解压,否则优先使用
COPY,语义更明确。
RUN — 执行命令
在构建过程中执行命令,每条 RUN 生成一个新的镜像层。
# 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 生效。
# Exec 形式(推荐)
CMD ["node", "server.js"]
# Shell 形式
CMD node server.js
# 作为 ENTRYPOINT 的默认参数
CMD ["--help"]
ENTRYPOINT — 入口点
设置容器启动时执行的主命令,与 CMD 配合使用。
# 固定入口
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 — 声明端口
声明容器运行时监听的端口(仅作文档说明,不会自动映射)。
EXPOSE 80
EXPOSE 443
EXPOSE 3000/tcp
EXPOSE 5000/udp
LABEL — 元数据
为镜像添加键值对形式的元数据。
LABEL maintainer="dev@example.com"
LABEL version="1.0"
LABEL description="My application"
# 单行写法
LABEL maintainer="dev@example.com" version="1.0"
4.1.3 构建镜像
基本构建命令
# 在 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
# 当前目录作为上下文
docker build -t myapp .
# 指定其他目录作为上下文
docker build -t myapp /path/to/context
# 使用 URL 作为上下文
docker build -t myapp https://github.com/user/repo.git
查看构建过程
# 查看镜像构建历史
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 镜像标签
标签用于标识镜像的不同版本,格式为 仓库名:标签。
# 构建时指定标签
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 — 环境变量
设置容器运行时的环境变量,在构建阶段和容器运行时均可用。
# 设置单个变量
ENV NODE_ENV=production
# 设置多个变量
ENV APP_HOME=/app \
APP_PORT=3000 \
LOG_LEVEL=info
# 在后续指令中引用
WORKDIR $APP_HOME
EXPOSE $APP_PORT
# 运行时覆盖环境变量
docker run -e NODE_ENV=development myapp
docker run --env-file .env myapp
ARG — 构建参数
定义构建时使用的变量,仅在 docker build 过程中有效,不会保留到运行时。
# 定义构建参数(可设默认值)
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"
# 构建时传入参数
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 中声明匿名卷挂载点,容器启动时自动创建匿名卷。
# 声明单个卷
VOLUME /data
# 声明多个卷
VOLUME ["/data", "/logs", "/config"]
VOLUME 指令的行为:
FROM mysql:8.0
# 声明数据目录为卷
VOLUME /var/lib/mysql
# 注意:VOLUME 之后对该目录的修改不会生效!
RUN echo "test" > /var/lib/mysql/test.txt # ❌ 无效
# 运行时会自动创建匿名卷
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 会定期执行检查命令判断容器是否健康。
# 基本用法
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 |
不同应用类型的健康检查示例:
# 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
查看健康状态:
# 查看容器健康状态
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 前端应用示例
# ========== 阶段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 减少镜像层数
每条 RUN、COPY、ADD 指令都会创建一个新的镜像层。合并指令可以减少层数,缩小镜像体积。
❌ 不推荐 — 过多层数:
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/*
✅ 推荐 — 合并为单层:
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
wget \
git \
&& rm -rf /var/lib/apt/lists/*
其他减少层数的技巧:
# ❌ 多条 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 按指令顺序构建,如果某层的输入未改变,会直接使用缓存。将变化频率低的指令放在前面,变化频率高的放在后面。
❌ 不推荐 — 源码变动导致依赖重装:
FROM node:18-alpine
WORKDIR /app
COPY . . # 任何文件改变都会使缓存失效
RUN npm install # 每次都要重新安装依赖
CMD ["node", "server.js"]
✅ 推荐 — 分离依赖安装与源码复制:
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 文件用于排除不需要发送到构建上下文的文件,减小上下文体积,加快构建速度,避免敏感信息泄露。
# .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/
效果对比:
# 无 .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 镜像变体:
node:18 # ~1GB,包含完整开发工具
node:18-slim # ~200MB,精简版 Debian
node:18-alpine # ~170MB,基于 Alpine Linux
选择建议:
# 开发/调试阶段 — 功能完整
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 用户运行,这是一个安全隐患。应创建并切换到普通用户。
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"]
验证用户身份:
# 进入容器查看当前用户
docker exec -it my-container whoami
# appuser
# 查看进程运行用户
docker exec -it my-container ps aux
⚠️ 注意:
USER指令之前的RUN命令仍以 root 执行,因此安装软件包等操作应在USER之前完成。
4.4 镜像优化
4.4.1 镜像体积优化
清理不必要的文件
# ❌ 残留缓存
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.2.4 多阶段构建。
压缩与合并层
# 使用 --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(官方工具)
# 扫描本地镜像
docker scout cves myapp:1.0
# 快速概览
docker scout quickview myapp:1.0
# 查看改进建议
docker scout recommendations myapp:1.0
Trivy(开源工具,推荐)
# 安装 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 集成示例
# 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)叠加
- 容器运行时在最顶层添加一个可写层
- 层是可共享的:多个镜像可复用相同的底层
查看镜像层
# 查看各层大小
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. 将变化频率相同的操作放在同一层:
# ✅ 系统依赖(变化很少)— 放在前面
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. 避免在高层删除低层文件:
# ❌ 文件仍保留在 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.base
FROM node:22-alpine
RUN apk add --no-cache make g++
# Dockerfile.app1 / Dockerfile.app2
FROM myteam/base:1.0 # 共享基础层
COPY . /app
完整优化示例对比:
# ========== 优化前 ==========
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 格式描述应用的服务、网络和数据卷。
基本结构:
# 服务定义
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 基本命令
# ========== 启动与停止 ==========
# 启动所有服务(后台运行)
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
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 控制服务启动顺序,支持两种写法:
简单依赖(仅控制启动顺序):
services:
app:
depends_on:
- db # db 先启动,但不等它就绪
- cache
条件依赖(等待服务就绪):
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 中定义
services:
app:
environment:
- NODE_ENV=production
- PORT=3000
- DB_HOST=db
方式二:使用 .env 文件
# .env(与 docker-compose.yml 同目录)
NODE_ENV=production
PORT=3000
DB_HOST=db
DB_PASSWORD=secret123
services:
app:
env_file:
- .env
方式三:在 yml 中引用宿主机环境变量
services:
app:
environment:
- NODE_ENV=${NODE_ENV:-production} # 默认值 production
- DB_PASSWORD=${DB_PASSWORD} # 从宿主机环境变量读取
优先级(高 → 低):
docker compose run -e命令行参数environment字段直接定义env_file文件- Dockerfile 中的
ENV
5.2.4 网络和数据卷
网络
Compose 会自动为项目创建一个默认网络,所有服务自动加入,可通过服务名互相访问。
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
自定义网络(隔离不同服务组):
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
数据卷
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:
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:
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;
}
}
}
关键点:
upstream中server app:3000的app是 Compose 服务名。Docker 内置 DNS 会将app解析为所有 app 容器的 IP,Nginx 自动轮询分发请求。
部署与管理:
# 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(本地启动测试的镜像仓库,用于模拟)
docker run \
-d \ # 后台运行容器
-p 5000:5000 \ # 将宿主机 5000 端口映射到容器 5000 端口(Registry 默认端口)
--name registry \ # 容器命名为 registry,方便后续管理
--restart unless-stopped \ # 异常退出自动重启,手动 stop 除外
registry:2 # 使用官方 Registry v2 镜像
步骤一:服务器上的 docker-compose.prod.yml
# /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 工作流
# .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中的构建和推送
# 模拟:
# 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