Use a better structure

This commit is contained in:
Baohua Yang
2026-02-09 09:32:05 -08:00
parent 2264a7ae72
commit 784c989789
167 changed files with 2462 additions and 2462 deletions

View File

@@ -1,6 +1,6 @@
# 获取镜像
## 获取镜像
## docker pull 命令
### docker pull 命令
从镜像仓库获取镜像的命令是 `docker pull`
@@ -8,7 +8,7 @@
docker pull [选项] [Registry地址/]仓库名[:标签]
```
### 镜像名称格式
#### 镜像名称格式
```
docker.io / library / ubuntu : 24.04
@@ -25,31 +25,31 @@ Registry地址 用户名 仓库名 标签
| 仓库名 | 镜像名称 | 必须指定 |
| 标签 | 版本标识 | `latest` |
### 示例
#### 示例
```bash
# 完整格式
## 完整格式
$ docker pull docker.io/library/ubuntu:24.04
# 省略 Registry默认 Docker Hub
## 省略 Registry默认 Docker Hub
$ docker pull library/ubuntu:24.04
# 省略 library官方镜像
## 省略 library官方镜像
$ docker pull ubuntu:24.04
# 省略标签默认 latest
## 省略标签默认 latest
$ docker pull ubuntu
# 拉取第三方镜像
## 拉取第三方镜像
$ docker pull bitnami/redis:latest
# 从其他 Registry 拉取
## 从其他 Registry 拉取
$ docker pull ghcr.io/username/myapp:v1.0
```
---
## 下载过程解析
### 下载过程解析
```bash
$ docker pull ubuntu:24.04
@@ -62,7 +62,7 @@ Status: Downloaded newer image for ubuntu:24.04
docker.io/library/ubuntu:24.04
```
### 输出解读
#### 输出解读
| 输出内容 | 说明 |
|---------|------|
@@ -71,7 +71,7 @@ docker.io/library/ubuntu:24.04
| `Digest: sha256:...` | 镜像内容的唯一摘要 |
| `docker.io/library/ubuntu:24.04` | 镜像的完整名称 |
### 分层下载
#### 分层下载
从输出可以看到镜像是**分层下载**
@@ -91,7 +91,7 @@ docker.io/library/ubuntu:24.04
---
## 常用选项
### 常用选项
| 选项 | 说明 | 示例 |
|------|------|------|
@@ -99,7 +99,7 @@ docker.io/library/ubuntu:24.04
| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |
| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |
### 指定平台
#### 指定平台
Apple Silicon Mac 上拉取 x86 镜像
@@ -109,15 +109,15 @@ $ docker pull --platform linux/amd64 nginx
---
## 拉取后运行
### 拉取后运行
拉取镜像后可以基于它启动容器
```bash
# 拉取镜像
## 拉取镜像
$ docker pull ubuntu:24.04
# 运行容器
## 运行容器
$ docker run -it --rm ubuntu:24.04 bash
root@e7009c6ce357:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
@@ -137,7 +137,7 @@ root@e7009c6ce357:/# exit
---
## 镜像加速
### 镜像加速
Docker Hub 下载可能较慢可以配置镜像加速器
@@ -155,16 +155,16 @@ root@e7009c6ce357:/# exit
```bash
$ sudo systemctl restart docker # Linux
# 或在 Docker Desktop 中重启
## 或在 Docker Desktop 中重启
```
详见 [镜像加速器](../install/mirror.md) 章节
详见 [镜像加速器](../install/3.9_mirror.md) 章节
---
## 验证镜像完整性
### 验证镜像完整性
### 查看镜像摘要
#### 查看镜像摘要
```bash
$ docker images --digests ubuntu
@@ -172,7 +172,7 @@ REPOSITORY TAG DIGEST
ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 ca2b0f26964c
```
### 使用摘要拉取
#### 使用摘要拉取
用摘要拉取可确保获取完全相同的镜像
@@ -184,15 +184,15 @@ $ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9
---
## 常见问题
### 常见问题
### Q: 下载速度很慢
#### Q: 下载速度很慢
1. 配置镜像加速器
2. 检查网络连接
3. 尝试拉取更小的镜像版本 `alpine` 变体
### Q: 提示镜像不存在
#### Q: 提示镜像不存在
```bash
Error: pull access denied, repository does not exist
@@ -203,19 +203,19 @@ Error: pull access denied, repository does not exist
- 私有镜像未登录需要 `docker login`
- 镜像确实不存在
### Q: 磁盘空间不足
#### Q: 磁盘空间不足
```bash
# 清理未使用的镜像
## 清理未使用的镜像
$ docker image prune
# 清理所有未使用资源
## 清理所有未使用资源
$ docker system prune
```
---
## 本章小结
### 本章小结
| 操作 | 命令 |
|------|------|
@@ -224,9 +224,9 @@ $ docker system prune
| 指定平台 | `docker pull --platform linux/amd64 镜像名` |
| 用摘要拉取 | `docker pull 镜像名@sha256:...` |
## 延伸阅读
### 延伸阅读
- [列出镜像](list.md)查看本地镜像
- [删除镜像](rm.md)清理本地镜像
- [镜像加速器](../install/mirror.md)加速镜像下载
- [Docker Hub](../repository/dockerhub.md)官方镜像仓库
- [列出镜像](4.2_list.md)查看本地镜像
- [删除镜像](4.3_rm.md)清理本地镜像
- [镜像加速器](../install/3.9_mirror.md)加速镜像下载
- [Docker Hub](../repository/6.1_dockerhub.md)官方镜像仓库

View File

@@ -1,6 +1,6 @@
# 列出镜像
## 列出镜像
## 基本用法
### 基本用法
查看本地已下载的镜像
@@ -17,7 +17,7 @@ ubuntu noble 329ed837d508 3 days ago 78MB
---
## 输出字段说明
### 输出字段说明
| 字段 | 说明 |
|------|------|
@@ -27,22 +27,22 @@ ubuntu noble 329ed837d508 3 days ago 78MB
| **CREATED** | 创建时间 |
| **SIZE** | 本地占用空间 |
### 同一镜像多个标签
#### 同一镜像多个标签
注意上面的 `ubuntu:24.04` `ubuntu:noble` 拥有相同的 IMAGE ID它们是同一个镜像的不同标签只占用一份存储空间
---
## 理解镜像大小
### 理解镜像大小
### 本地大小 vs Hub 显示大小
#### 本地大小 vs Hub 显示大小
| 位置 | 显示大小 | 说明 |
|------|---------|------|
| Docker Hub | 29MB | 压缩后的网络传输大小 |
| docker image ls | 78MB | 本地解压后的实际大小 |
### 实际磁盘占用
#### 实际磁盘占用
由于镜像是分层存储不同镜像可能共享相同的层
@@ -56,7 +56,7 @@ ubuntu:24.04 nginx:latest redis:latest
因此`docker image ls` 中各镜像大小之和 > 实际磁盘占用
### 查看实际空间占用
#### 查看实际空间占用
```bash
$ docker system df
@@ -69,12 +69,12 @@ Build Cache 0 0 0B 0B
---
## 过滤镜像
### 过滤镜像
### 按仓库名过滤
#### 按仓库名过滤
```bash
# 列出所有 ubuntu 镜像
## 列出所有 ubuntu 镜像
$ docker images ubuntu
REPOSITORY TAG IMAGE ID SIZE
ubuntu 24.04 329ed837d508 78MB
@@ -82,7 +82,7 @@ ubuntu noble 329ed837d508 78MB
ubuntu 22.04 a1b2c3d4e5f6 72MB
```
### 按仓库名和标签过滤
#### 按仓库名和标签过滤
```bash
$ docker images ubuntu:24.04
@@ -90,7 +90,7 @@ REPOSITORY TAG IMAGE ID SIZE
ubuntu 24.04 329ed837d508 78MB
```
### 使用过滤器 --filter
#### 使用过滤器 --filter
| 过滤条件 | 说明 | 示例 |
|---------|------|------|
@@ -101,21 +101,21 @@ ubuntu 24.04 329ed837d508 78MB
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
```bash
# 列出 nginx 之后创建的镜像
## 列出 nginx 之后创建的镜像
$ docker images -f since=nginx:latest
# 列出所有带 latest 标签的镜像
## 列出所有带 latest 标签的镜像
$ docker images -f reference='*:latest'
# 列出带特定 LABEL 的镜像
## 列出带特定 LABEL 的镜像
$ docker images -f label=maintainer=example@email.com
```
---
## 虚悬镜像Dangling Images
### 虚悬镜像Dangling Images
### 什么是虚悬镜像
#### 什么是虚悬镜像
仓库名和标签都显示为 `<none>` 的镜像
@@ -125,26 +125,26 @@ REPOSITORY TAG IMAGE ID SIZE
<none> <none> 00285df0df87 342MB
```
### 产生原因
#### 产生原因
1. **镜像重新构建**新镜像使用了旧镜像的标签旧镜像标签被移除
2. **docker pull 更新**拉取更新版本时旧版本失去标签
### 处理虚悬镜像
#### 处理虚悬镜像
```bash
# 列出虚悬镜像
## 列出虚悬镜像
$ docker images -f dangling=true
# 删除虚悬镜像
## 删除虚悬镜像
$ docker image prune
```
---
## 中间层镜像
### 中间层镜像
### 查看所有镜像包含中间层
#### 查看所有镜像包含中间层
```bash
$ docker images -a
@@ -156,9 +156,9 @@ $ docker images -a
---
## 格式化输出
### 格式化输出
### 只输出 ID
#### 只输出 ID
```bash
$ docker images -q
@@ -170,20 +170,20 @@ $ docker images -q
常用于配合其他命令
```bash
# 删除所有镜像
## 删除所有镜像
$ docker rmi $(docker images -q)
# 删除所有 redis 镜像
## 删除所有 redis 镜像
$ docker rmi $(docker images -q redis)
```
### 显示完整 ID
#### 显示完整 ID
```bash
$ docker images --no-trunc
```
### 显示摘要
#### 显示摘要
```bash
$ docker images --digests
@@ -191,18 +191,18 @@ REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
```
### 自定义格式
#### 自定义格式
使用 Go 模板语法自定义输出
```bash
# 只显示 ID 和仓库名
## 只显示 ID 和仓库名
$ docker images --format "{{.ID}}: {{.Repository}}"
5f515359c7f8: redis
05a60462f8ba: nginx
329ed837d508: ubuntu
# 表格形式带标题
## 表格形式带标题
$ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
REPOSITORY TAG SIZE
redis latest 183MB
@@ -210,7 +210,7 @@ nginx latest 181MB
ubuntu 24.04 78MB
```
### 可用模板字段
#### 可用模板字段
| 字段 | 说明 |
|------|------|
@@ -224,22 +224,22 @@ ubuntu 24.04 78MB
---
## 常用命令组合
### 常用命令组合
```bash
# 列出所有镜像及其大小按大小排序需要系统 sort 命令
## 列出所有镜像及其大小按大小排序需要系统 sort 命令
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -h
# 查找大于 500MB 的镜像
## 查找大于 500MB 的镜像
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | grep -E "^[0-9]+GB|^[5-9][0-9]{2}MB"
# 导出镜像列表
## 导出镜像列表
$ docker images --format "{{.Repository}}:{{.Tag}}" > images.txt
```
---
## 本章小结
### 本章小结
| 操作 | 命令 |
|------|------|
@@ -251,8 +251,8 @@ $ docker images --format "{{.Repository}}:{{.Tag}}" > images.txt
| 自定义格式 | `docker images --format "..."` |
| 查看空间占用 | `docker system df` |
## 延伸阅读
### 延伸阅读
- [获取镜像](pull.md) Registry 拉取镜像
- [删除镜像](rm.md)清理本地镜像
- [镜像](../02_basic_concept/image.md)理解镜像概念
- [获取镜像](4.1_pull.md) Registry 拉取镜像
- [删除镜像](4.3_rm.md)清理本地镜像
- [镜像](../02_basic_concept/2.1_image.md)理解镜像概念

View File

@@ -1,6 +1,6 @@
# 删除本地镜像
## 删除本地镜像
## 基本用法
### 基本用法
使用 `docker image rm` 删除本地镜像
@@ -12,7 +12,7 @@ $ docker image rm [选项] <镜像1> [<镜像2> ...]
---
## 镜像标识方式
### 镜像标识方式
删除镜像时可以使用多种方式指定镜像
@@ -23,7 +23,7 @@ $ docker image rm [选项] <镜像1> [<镜像2> ...]
| **镜像名:标签** | 仓库名和标签 | `docker rmi redis:alpine` |
| **镜像摘要** | 精确的内容摘要 | `docker rmi nginx@sha256:...` |
### 使用短 ID 删除
#### 使用短 ID 删除
```bash
$ docker image ls
@@ -31,13 +31,13 @@ REPOSITORY TAG IMAGE ID SIZE
redis alpine 501ad78535f0 30MB
nginx latest e43d811ce2f4 142MB
# 只需输入足够区分的前几位
## 只需输入足够区分的前几位
$ docker rmi 501
Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
```
### 使用镜像名删除
#### 使用镜像名删除
```bash
$ docker rmi redis:alpine
@@ -45,23 +45,23 @@ Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
```
### 使用摘要删除
#### 使用摘要删除
摘要删除最精确适用于 CI/CD 场景
```bash
# 查看镜像摘要
## 查看镜像摘要
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
# 使用摘要删除
## 使用摘要删除
$ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
```
---
## 理解输出信息
### 理解输出信息
删除镜像时会看到两类信息**Untagged** **Deleted**
@@ -74,14 +74,14 @@ Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
```
### Untagged vs Deleted
#### Untagged vs Deleted
| 操作 | 含义 |
|------|------|
| **Untagged** | 移除镜像的标签 |
| **Deleted** | 删除镜像的存储层 |
### 删除流程
#### 删除流程
```
docker rmi redis:alpine
@@ -106,51 +106,51 @@ docker rmi redis:alpine
---
## 批量删除
### 批量删除
### 删除所有虚悬镜像
#### 删除所有虚悬镜像
虚悬镜像dangling没有标签的镜像通常是旧版本被新版本覆盖后产生的
```bash
# 查看虚悬镜像
## 查看虚悬镜像
$ docker images -f dangling=true
# 删除虚悬镜像
## 删除虚悬镜像
$ docker image prune
# 不提示确认
## 不提示确认
$ docker image prune -f
```
### 删除所有未使用的镜像
#### 删除所有未使用的镜像
```bash
# 删除所有没有被容器使用的镜像
## 删除所有没有被容器使用的镜像
$ docker image prune -a
# 保留最近 24 小时的
## 保留最近 24 小时的
$ docker image prune -a --filter "until=24h"
```
### 按条件删除
#### 按条件删除
```bash
# 删除所有 redis 镜像
## 删除所有 redis 镜像
$ docker rmi $(docker images -q redis)
# 删除 mongo:8.0 之前的所有镜像
## 删除 mongo:8.0 之前的所有镜像
$ docker rmi $(docker images -q -f before=mongo:8.0)
# 删除某个时间之前的镜像
## 删除某个时间之前的镜像
$ docker image prune -a --filter "until=168h" # 7天前
```
---
## 删除失败的常见原因
### 删除失败的常见原因
### 原因一有容器依赖
#### 原因一有容器依赖
```bash
$ docker rmi nginx
@@ -161,15 +161,15 @@ Error: conflict: unable to remove repository reference "nginx"
**解决方案**
```bash
# 方案1先删除依赖的容器
## 方案1先删除依赖的容器
$ docker rm abc123
$ docker rmi nginx
# 方案2强制删除镜像容器仍可运行但无法再创建新容器
## 方案2强制删除镜像容器仍可运行但无法再创建新容器
$ docker rmi -f nginx
```
### 原因二多个标签指向同一镜像
#### 原因二多个标签指向同一镜像
```bash
$ docker images
@@ -179,10 +179,10 @@ ubuntu latest ca2b0f26964c # 同一个镜像
$ docker rmi ubuntu:24.04
Untagged: ubuntu:24.04
# 只是移除标签镜像仍存在因为还有 ubuntu:latest 指向它
## 只是移除标签镜像仍存在因为还有 ubuntu:latest 指向它
```
### 原因三被其他镜像依赖中间层
#### 原因三被其他镜像依赖中间层
```bash
$ docker rmi some_base_image
@@ -193,7 +193,7 @@ Error: image has dependent child images
---
## 常用过滤条件
### 常用过滤条件
| 过滤条件 | 说明 | 示例 |
|---------|------|------|
@@ -205,26 +205,26 @@ Error: image has dependent child images
---
## 清理策略
### 清理策略
### 开发环境
#### 开发环境
```bash
# 定期清理虚悬镜像
## 定期清理虚悬镜像
$ docker image prune -f
# 一键清理所有未使用资源
## 一键清理所有未使用资源
$ docker system prune -a
```
### CI/CD 环境
#### CI/CD 环境
```bash
# 只保留最近使用的镜像
## 只保留最近使用的镜像
$ docker image prune -a --filter "until=72h" -f
```
### 查看空间占用
#### 查看空间占用
```bash
$ docker system df
@@ -237,7 +237,7 @@ Build Cache 0 0 0B 0B
---
## 本章小结
### 本章小结
| 操作 | 命令 |
|------|------|
@@ -248,8 +248,8 @@ Build Cache 0 0 0B 0B
| 批量删除 | `docker rmi $(docker images -q -f ...)` |
| 查看空间占用 | `docker system df` |
## 延伸阅读
### 延伸阅读
- [列出镜像](list.md)查看和过滤镜像
- [删除容器](../05_container/rm.md)清理容器
- [列出镜像](4.2_list.md)查看和过滤镜像
- [删除容器](../05_container/4.3_rm.md)清理容器
- [数据卷](../07_data_network/data/volume.md)清理数据卷

View File

@@ -1,4 +1,4 @@
# 利用 commit 理解镜像构成
## 利用 commit 理解镜像构成
> 注意如果您是初学者您可以暂时跳过后面的内容直接学习 [容器](../05_container/) 一节
@@ -120,7 +120,7 @@ docker run --name web2 -d -p 81:80 nginx:v2
至此我们第一次完成了定制镜像使用的是 `docker commit` 命令手动操作给旧的镜像添加了新的一层形成新的镜像对镜像多层存储应该有了更直观的感觉
## 慎用 `docker commit`
### 慎用 `docker commit`
使用 `docker commit` 命令虽然可以比较直观的帮助理解镜像分层存储的概念但是实际环境中并不会这样使用

View File

@@ -1,10 +1,10 @@
# 使用 Dockerfile 定制镜像
## 使用 Dockerfile 定制镜像
从刚才的 `docker commit` 的学习中我们可以了解到镜像的定制实际上就是定制每一层所添加的配置文件如果我们可以把每一层修改安装构建操作的命令都写入一个脚本用这个脚本来构建定制镜像那么之前提及的无法重复的问题镜像构建透明性的问题体积的问题就都会解决这个脚本就是 Dockerfile
Dockerfile 是一个文本文件其内包含了一条条的 **指令(Instruction)**每一条指令构建一层因此每一条指令的内容就是描述该层应当如何构建
## 使用 docker init 快速创建推荐
### 使用 docker init 快速创建推荐
Docker 提供了 `docker init` 命令可以根据项目类型自动生成 Dockerfile.dockerignore compose.yaml 文件
@@ -14,7 +14,7 @@ $ docker init
该命令会交互式地询问项目类型 GoNode.jsPythonRust 并生成符合最佳实践的配置文件对于新项目这是推荐的起步方式
## 手动创建 Dockerfile
### 手动创建 Dockerfile
还以之前定制 `nginx` 镜像为例这次我们使用 Dockerfile 来定制
@@ -35,7 +35,7 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
这个 Dockerfile 很简单一共就两行涉及到了两条指令`FROM` `RUN`
## FROM 指定基础镜像
### FROM 指定基础镜像
所谓定制镜像那一定是以一个镜像为基础在其上进行定制就像我们之前运行了一个 `nginx` 镜像的容器再进行修改一样基础镜像是必须指定的 `FROM` 就是指定 **基础镜像**因此一个 `Dockerfile` `FROM` 是必备的指令并且必须是第一条指令
@@ -54,7 +54,7 @@ FROM scratch
不以任何系统为基础直接将可执行文件复制进镜像的做法并不罕见对于 Linux 下静态编译的程序来说并不需要有操作系统提供运行时支持所需的一切库都已经在可执行文件里了因此直接 `FROM scratch` 会让镜像体积更加小巧使用 [Go 语言](https://golang.google.cn/) 开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
## RUN 执行命令
### RUN 执行命令
`RUN` 指令是用来执行命令行命令的由于命令行的强大能力`RUN` 指令在定制镜像时是最常用的指令之一其格式有两种
@@ -114,7 +114,7 @@ RUN set -x; buildDeps='gcc libc6-dev make wget' \
很多人初学 Docker 制作出了很臃肿的镜像的原因之一就是忘记了每一层构建的最后一定要清理掉无关文件
## 构建镜像
### 构建镜像
好了让我们再回到之前定制的 nginx 镜像的 Dockerfile 现在我们明白了这个 Dockerfile 的内容那么让我们来构建这个镜像吧
@@ -142,7 +142,7 @@ docker build [选项] <上下文路径/URL/->
在这里我们指定了最终镜像的名称 `-t nginx:v3`构建成功后我们可以像之前运行 `nginx:v2` 那样来运行这个镜像其结果会和 `nginx:v2` 一样
## 镜像构建上下文Context
### 镜像构建上下文Context
如果注意会看到 `docker build` 命令最后有一个 `.``.` 表示当前目录 `Dockerfile` 就在当前目录因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径这么理解其实是不准确的如果对应上面的命令格式你可能会发现这是在指定 **上下文路径**那么什么是上下文呢
@@ -182,15 +182,15 @@ Sending build context to Docker daemon 2.048 kB
当然一般大家习惯性的会使用默认的文件名 `Dockerfile`以及会将其置于镜像构建上下文目录中
## 其它 `docker build` 的用法
### 其它 `docker build` 的用法
### 直接用 Git repo 进行构建
#### 直接用 Git repo 进行构建
或许你已经注意到了`docker build` 还支持从 URL 构建比如可以直接从 Git repo 中构建
```bash
# $env:DOCKER_BUILDKIT=0
# export DOCKER_BUILDKIT=0
## $env:DOCKER_BUILDKIT=0
## export DOCKER_BUILDKIT=0
$ docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world
@@ -207,7 +207,7 @@ Successfully built 038ad4142d2b
这行命令指定了构建所需的 Git repo并且指定分支为 `master`构建目录为 `/amd64/hello-world/`然后 Docker 就会自己去 `git clone` 这个项目切换到指定分支并进入到指定目录后开始构建
### 用给定的 tar 压缩包构建
#### 用给定的 tar 压缩包构建
```bash
$ docker build http://server/context.tar.gz
@@ -215,7 +215,7 @@ $ docker build http://server/context.tar.gz
如果所给出的 URL 不是个 Git repo而是个 `tar` 压缩包那么 Docker 引擎会下载这个包并自动解压缩以其作为上下文开始构建
### 从标准输入中读取 Dockerfile 进行构建
#### 从标准输入中读取 Dockerfile 进行构建
```bash
docker build - < Dockerfile
@@ -229,7 +229,7 @@ cat Dockerfile | docker build -
如果标准输入传入的是文本文件则将其视为 `Dockerfile`并开始构建这种形式由于直接从标准输入中读取 Dockerfile 的内容它没有上下文因此不可以像其他方法那样可以将本地文件 `COPY` 进镜像之类的事情
### 从标准输入中读取上下文压缩包进行构建
#### 从标准输入中读取上下文压缩包进行构建
```bash
$ docker build - < context.tar.gz

View File

@@ -1,8 +1,8 @@
# 其它制作镜像的方式
## 其它制作镜像的方式
除了标准的使用 `Dockerfile` 生成镜像的方法外由于各种特殊需求和历史原因还提供了一些其它方法用以生成镜像
## rootfs 压缩包导入
### rootfs 压缩包导入
格式`docker import [选项] <文件>|<URL>|- [<仓库名>[:<标签>]]`
@@ -37,11 +37,11 @@ IMAGE CREATED CREATED BY SIZE
f477a6e18e98 About a minute ago 214.9 MB Imported from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
```
## Docker 镜像的导入和导出 `docker save` `docker load`
### Docker 镜像的导入和导出 `docker save` `docker load`
Docker 还提供了 `docker save` `docker load` 命令用以将镜像保存为一个文件然后传输到另一个位置上再加载进来这是在没有 Docker Registry 时的做法现在已经不推荐镜像迁移应该直接使用 Docker Registry无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以
### 保存镜像
#### 保存镜像
使用 `docker save` 命令可以将镜像保存为归档文件

View File

@@ -1,4 +1,4 @@
# 镜像的实现原理
## 镜像的实现原理
Docker 镜像是怎么实现增量的修改和维护的

View File

@@ -1,4 +1,4 @@
# 使用 Docker 镜像
# 第四章 使用镜像
在之前的介绍中我们知道镜像是 Docker 的三大组件之一

View File

@@ -1,3 +1,3 @@
# Dockerfile 指令详解
## Dockerfile 指令详解
我们已经介绍了 `FROM``RUN`还提及了 `COPY`, `ADD`其实 `Dockerfile` 功能很强大它提供了十多个指令下面我们继续讲解其他的指令

View File

@@ -1,6 +1,6 @@
# ADD 更高级的复制文件
## ADD 更高级的复制文件
## 基本语法
### 基本语法
```docker
ADD [选项] <源路径>... <目标路径>
@@ -13,7 +13,7 @@ ADD [选项] ["<源路径>", ... "<目标路径>"]
---
## ADD vs COPY
### ADD vs COPY
| 特性 | COPY | ADD |
|------|------|-----|
@@ -27,12 +27,12 @@ ADD [选项] ["<源路径>", ... "<目标路径>"]
---
## 自动解压功能
### 自动解压功能
### 基本用法
#### 基本用法
```docker
# 自动解压 tar.gz 到目标目录
## 自动解压 tar.gz 到目标目录
ADD app.tar.gz /app/
```
@@ -42,7 +42,7 @@ ADD 会识别并解压以下格式:
- `.tar.bz2` / `.tbz2`
- `.tar.xz` / `.txz`
### 实际应用
#### 实际应用
官方基础镜像通常使用 ADD 解压根文件系统
@@ -51,7 +51,7 @@ FROM scratch
ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
```
### 解压过程
#### 解压过程
```
ADD app.tar.gz /app/
@@ -68,16 +68,16 @@ app.tar.gz 包含: /app/ 目录结果:
---
## URL 下载功能不推荐
### URL 下载功能不推荐
### 基本用法
#### 基本用法
```docker
# 从 URL 下载文件
## 从 URL 下载文件
ADD https://example.com/app.zip /app/app.zip
```
### 为什么不推荐
#### 为什么不推荐
| 问题 | 说明 |
|------|------|
@@ -86,14 +86,14 @@ ADD https://example.com/app.zip /app/app.zip
| 缓存问题 | URL 内容变化时不会重新下载 |
| 层数增加 | 需要额外 RUN 清理 |
### 推荐替代方案
#### 推荐替代方案
```docker
# ❌ 不推荐:使用 ADD 下载
## ❌ 不推荐:使用 ADD 下载
ADD https://example.com/app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz
# ✅ 推荐:使用 RUN + curl
## ✅ 推荐:使用 RUN + curl
RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
```
@@ -104,7 +104,7 @@ RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
---
## 修改文件所有者
### 修改文件所有者
```docker
ADD --chown=node:node app.tar.gz /app/
@@ -113,43 +113,43 @@ ADD --chown=1000:1000 files/ /app/
---
## 何时使用 ADD
### 何时使用 ADD
### 适合使用 ADD
#### 适合使用 ADD
```docker
# 解压本地 tar 文件
## 解压本地 tar 文件
FROM scratch
ADD rootfs.tar.gz /
# 解压应用包
## 解压应用包
ADD dist.tar.gz /app/
```
### 不适合使用 ADD
#### 不适合使用 ADD
```docker
# 复制普通文件(用 COPY
## 复制普通文件(用 COPY
ADD package.json /app/ # ❌
COPY package.json /app/ # ✅
# 下载文件(用 RUN + curl
## 下载文件(用 RUN + curl
ADD https://example.com/file / # ❌
RUN curl -fsSL ... -o /file # ✅
# 需要保留 tar 不解压(用 COPY
## 需要保留 tar 不解压(用 COPY
ADD archive.tar.gz /archives/ # ❌ 会解压
COPY archive.tar.gz /archives/ # ✅ 保持原样
```
---
## 缓存行为
### 缓存行为
ADD 可能导致构建缓存失效
```docker
# 如果 app.tar.gz 内容变化,此层及后续层都需重建
## 如果 app.tar.gz 内容变化,此层及后续层都需重建
ADD app.tar.gz /app/
RUN npm install
```
@@ -157,46 +157,46 @@ RUN npm install
**优化建议**
```docker
# 先复制依赖文件
## 先复制依赖文件
COPY package*.json /app/
RUN npm install
# 再添加应用代码
## 再添加应用代码
ADD app.tar.gz /app/
```
---
## 最佳实践
### 最佳实践
### 1. 默认使用 COPY
#### 1. 默认使用 COPY
```docker
# ✅ 大多数场景使用 COPY
## ✅ 大多数场景使用 COPY
COPY . /app/
```
### 2. 仅在需要解压时使用 ADD
#### 2. 仅在需要解压时使用 ADD
```docker
# ✅ 自动解压场景
## ✅ 自动解压场景
ADD app.tar.gz /app/
```
### 3. 不要用 ADD 下载文件
#### 3. 不要用 ADD 下载文件
```docker
# ❌ 避免
## ❌ 避免
ADD https://example.com/file.tar.gz /tmp/
# ✅ 推荐
## ✅ 推荐
RUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app
```
### 4. 解压后清理
#### 4. 解压后清理
```docker
# 如果需要控制解压过程
## 如果需要控制解压过程
COPY app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && \
rm /tmp/app.tar.gz
@@ -204,7 +204,7 @@ RUN tar -xzf /tmp/app.tar.gz -C /app && \
---
## 本章小结
### 本章小结
| 场景 | 推荐指令 |
|------|---------|
@@ -214,8 +214,8 @@ RUN tar -xzf /tmp/app.tar.gz -C /app && \
| URL 下载 | `RUN curl` |
| 保持 tar 不解压 | `COPY` |
## 延伸阅读
### 延伸阅读
- [COPY 复制文件](copy.md)基本复制操作
- [多阶段构建](../multistage-builds.md)减少镜像体积
- [最佳实践](../../15_appendix/best_practices.md)Dockerfile 编写指南
- [最佳实践](../../15_appendix/15.1_best_practices.md)Dockerfile 编写指南

View File

@@ -1,6 +1,6 @@
# ARG 构建参数
## ARG 构建参数
## 基本语法
### 基本语法
```docker
ARG <参数名>[=<默认值>]
@@ -10,7 +10,7 @@ ARG <参数名>[=<默认值>]
---
## ARG vs ENV
### ARG vs ENV
| 特性 | ARG | ENV |
|------|-----|-----|
@@ -31,79 +31,79 @@ ARG <参数名>[=<默认值>]
---
## 基本用法
### 基本用法
### 定义和使用
#### 定义和使用
```docker
# 定义有默认值的 ARG
## 定义有默认值的 ARG
ARG NODE_VERSION=20
# 使用 ARG
## 使用 ARG
FROM node:${NODE_VERSION}-alpine
RUN echo "Using Node.js $NODE_VERSION"
```
### 构建时覆盖
#### 构建时覆盖
```bash
# 使用默认值
## 使用默认值
$ docker build -t myapp .
# 覆盖默认值
## 覆盖默认值
$ docker build --build-arg NODE_VERSION=18 -t myapp .
```
---
## ARG 的作用域
### ARG 的作用域
### FROM 之前的 ARG
#### FROM 之前的 ARG
```docker
# FROM 之前的 ARG 只能用于 FROM 指令
## FROM 之前的 ARG 只能用于 FROM 指令
ARG REGISTRY=docker.io
ARG IMAGE_NAME=node
FROM ${REGISTRY}/${IMAGE_NAME}:20
# ❌ 这里无法使用上面的 ARG
## ❌ 这里无法使用上面的 ARG
RUN echo $REGISTRY # 输出空
```
### FROM 之后重新声明
#### FROM 之后重新声明
```docker
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine
# 需要再次声明才能使用
## 需要再次声明才能使用
ARG NODE_VERSION
RUN echo "Node version: $NODE_VERSION"
```
### 多阶段构建中的 ARG
#### 多阶段构建中的 ARG
```docker
ARG BASE_VERSION=alpine
FROM node:20-${BASE_VERSION} AS builder
# 需要重新声明
## 需要重新声明
ARG NODE_VERSION=20
RUN echo "Building with Node $NODE_VERSION"
FROM node:20-${BASE_VERSION}
# 每个阶段都需要重新声明
## 每个阶段都需要重新声明
ARG NODE_VERSION=20
RUN echo "Running with Node $NODE_VERSION"
```
---
## 常见使用场景
### 常见使用场景
### 1. 控制基础镜像版本
#### 1. 控制基础镜像版本
```docker
ARG ALPINE_VERSION=3.19
@@ -114,7 +114,7 @@ FROM alpine:${ALPINE_VERSION}
$ docker build --build-arg ALPINE_VERSION=3.18 .
```
### 2. 设置软件版本
#### 2. 设置软件版本
```docker
ARG NGINX_VERSION=1.25.0
@@ -122,7 +122,7 @@ ARG NGINX_VERSION=1.25.0
RUN curl -fsSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xz
```
### 3. 配置构建环境
#### 3. 配置构建环境
```docker
ARG BUILD_ENV=production
@@ -135,7 +135,7 @@ RUN if [ "$ENABLE_DEBUG" = "true" ]; then \
fi
```
### 4. 配置私有仓库
#### 4. 配置私有仓库
```docker
ARG NPM_TOKEN
@@ -146,29 +146,29 @@ RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc && \
```
```bash
# 构建时传入 token
## 构建时传入 token
$ docker build --build-arg NPM_TOKEN=xxx .
```
---
## ARG 传递给 ENV
### ARG 传递给 ENV
如果需要在运行时使用 ARG 的值
```docker
ARG VERSION=1.0.0
# 将 ARG 传递给 ENV
## 将 ARG 传递给 ENV
ENV APP_VERSION=$VERSION
# 运行时可用
## 运行时可用
CMD echo "App version: $APP_VERSION"
```
---
## 预定义 ARG
### 预定义 ARG
Docker 提供了一些预定义的 ARG无需声明即可使用
@@ -180,47 +180,47 @@ Docker 提供了一些预定义的 ARG无需声明即可使用
| `FTP_PROXY` | FTP 代理 |
```bash
# 构建时使用代理
## 构建时使用代理
$ docker build --build-arg HTTP_PROXY=http://proxy:8080 .
```
---
## 最佳实践
### 最佳实践
### 1. ARG 提供合理默认值
#### 1. ARG 提供合理默认值
```docker
# ✅ 好:有默认值
## ✅ 好:有默认值
ARG NODE_VERSION=20
# ⚠️ 需要每次传入
## ⚠️ 需要每次传入
ARG NODE_VERSION
```
### 2. 不要用 ARG 存储敏感信息
#### 2. 不要用 ARG 存储敏感信息
```docker
# ❌ 错误:密码会被记录在镜像历史中
## ❌ 错误:密码会被记录在镜像历史中
ARG DB_PASSWORD
RUN echo "password=$DB_PASSWORD" > /app/.env
# ✅ 正确:使用 secrets 或运行时环境变量
## ✅ 正确:使用 secrets 或运行时环境变量
```
### 3. 使用 ARG 提高构建灵活性
#### 3. 使用 ARG 提高构建灵活性
```docker
ARG BASE_IMAGE=python:3.12-slim
FROM ${BASE_IMAGE}
# 可以构建不同基础镜像的版本
# docker build --build-arg BASE_IMAGE=python:3.11-alpine .
## 可以构建不同基础镜像的版本
## docker build --build-arg BASE_IMAGE=python:3.11-alpine .
```
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -231,8 +231,8 @@ FROM ${BASE_IMAGE}
| **vs ENV** | ARG 仅构建时ENV 构建+运行时 |
| **安全** | 不要存储敏感信息 |
## 延伸阅读
### 延伸阅读
- [ENV 设置环境变量](env.md)运行时环境变量
- [FROM 指令](../../04_image/build.md)基础镜像指定
- [FROM 指令](../../04_image/4.5_build.md)基础镜像指定
- [多阶段构建](../multistage-builds.md)复杂构建场景

View File

@@ -1,6 +1,6 @@
# CMD 容器启动命令
## CMD 容器启动命令
## 什么是 CMD
### 什么是 CMD
`CMD` 指令用于指定容器启动时默认执行的命令它定义了容器的"主进程"
@@ -8,7 +8,7 @@
---
## 语法格式
### 语法格式
CMD 有三种格式
@@ -18,7 +18,7 @@ CMD 有三种格式:
| **shell 格式** | `CMD 命令 参数1 参数2` | 简单场景 |
| **参数格式** | `CMD ["参数1", "参数2"]` | 配合 ENTRYPOINT |
### exec 格式推荐
#### exec 格式推荐
```docker
CMD ["nginx", "-g", "daemon off;"]
@@ -31,7 +31,7 @@ CMD ["node", "server.js"]
- 正确接收信号 SIGTERM
- 无需 shell 解析
### shell 格式
#### shell 格式
```docker
CMD echo "Hello World"
@@ -41,10 +41,10 @@ CMD nginx -g "daemon off;"
**实际执行**会被包装为 `sh -c`
```docker
# 你写的
## 你写的
CMD echo $HOME
# 实际执行的
## 实际执行的
CMD ["sh", "-c", "echo $HOME"]
```
@@ -53,7 +53,7 @@ CMD ["sh", "-c", "echo $HOME"]
---
## exec 格式 vs shell 格式
### exec 格式 vs shell 格式
| 特性 | exec 格式 | shell 格式 |
|------|----------|-----------|
@@ -62,27 +62,27 @@ CMD ["sh", "-c", "echo $HOME"]
| 环境变量 | 需要 shell 包装 | 自动解析 |
| 推荐使用 | 大多数场景 | 需要 shell 特性时 |
### 信号传递问题示例
#### 信号传递问题示例
```docker
# ❌ shell 格式docker stop 会超时
## ❌ shell 格式docker stop 会超时
CMD node server.js
# 实际是 sh -c "node server.js"
# SIGTERM 发给 sh不会传递给 node
## 实际是 sh -c "node server.js"
## SIGTERM 发给 sh不会传递给 node
# ✅ exec 格式docker stop 正常工作
## ✅ exec 格式docker stop 正常工作
CMD ["node", "server.js"]
# SIGTERM 直接发给 node
## SIGTERM 直接发给 node
```
---
## 运行时覆盖 CMD
### 运行时覆盖 CMD
`docker run` 后的命令会覆盖 Dockerfile 中的 CMD
```bash
# ubuntu 默认 CMD 是 /bin/bash
## ubuntu 默认 CMD 是 /bin/bash
$ docker run -it ubuntu # 进入 bash
$ docker run ubuntu cat /etc/os-release # 覆盖为 cat 命令
```
@@ -98,16 +98,16 @@ CMD ["/bin/bash"] + cat /etc/os-release
---
## 经典错误容器立即退出
### 经典错误容器立即退出
### 错误示例
#### 错误示例
```docker
# ❌ 容器启动后立即退出
## ❌ 容器启动后立即退出
CMD service nginx start
```
### 原因分析
#### 原因分析
```
1. CMD service nginx start
@@ -123,26 +123,26 @@ CMD service nginx start
6. 容器主进程sh退出 → 容器停止
```
### 正确做法
#### 正确做法
```docker
# ✅ 让 nginx 在前台运行
## ✅ 让 nginx 在前台运行
CMD ["nginx", "-g", "daemon off;"]
```
---
## CMD vs ENTRYPOINT
### CMD vs ENTRYPOINT
| 指令 | 用途 | 运行时行为 |
|------|------|-----------|
| **CMD** | 默认命令 | `docker run` 参数会**覆盖** |
| **ENTRYPOINT** | 入口点 | `docker run` 参数会**追加**到它后面 |
### 单独使用 CMD
#### 单独使用 CMD
```docker
# Dockerfile
## Dockerfile
CMD ["curl", "-s", "http://example.com"]
```
@@ -151,10 +151,10 @@ $ docker run myimage # 执行默认命令
$ docker run myimage curl -v ... # 完全覆盖
```
### 搭配 ENTRYPOINT
#### 搭配 ENTRYPOINT
```docker
# Dockerfile
## Dockerfile
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
```
@@ -168,57 +168,57 @@ $ docker run myimage http://other.com # curl -s http://other.com参数覆盖
---
## 最佳实践
### 最佳实践
### 1. 优先使用 exec 格式
#### 1. 优先使用 exec 格式
```docker
# ✅ 推荐
## ✅ 推荐
CMD ["python", "app.py"]
# ⚠️ 仅在需要 shell 特性时使用
## ⚠️ 仅在需要 shell 特性时使用
CMD ["sh", "-c", "echo $PATH && python app.py"]
```
### 2. 确保应用在前台运行
#### 2. 确保应用在前台运行
```docker
# ✅ 前台运行
## ✅ 前台运行
CMD ["nginx", "-g", "daemon off;"]
CMD ["apache2ctl", "-D", "FOREGROUND"]
CMD ["java", "-jar", "app.jar"]
# ❌ 不要使用后台服务命令
## ❌ 不要使用后台服务命令
CMD service nginx start
CMD systemctl start nginx
```
### 3. 使用双引号
#### 3. 使用双引号
```docker
# ✅ 正确:双引号
## ✅ 正确:双引号
CMD ["node", "server.js"]
# ❌ 错误单引号JSON 不支持)
## ❌ 错误单引号JSON 不支持)
CMD ['node', 'server.js']
```
### 4. 配合 ENTRYPOINT 使用
#### 4. 配合 ENTRYPOINT 使用
```docker
# 用于可配置参数的场景
## 用于可配置参数的场景
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]
# 运行时可以覆盖端口
## 运行时可以覆盖端口
$ docker run myapp --port 9000
```
---
## 常见问题
### 常见问题
### Q: CMD 可以写多个吗
#### Q: CMD 可以写多个吗
不可以多个 CMD 只有最后一个生效
@@ -227,31 +227,31 @@ CMD ["echo", "first"]
CMD ["echo", "second"] # 只有这个生效
```
### Q: 如何在 CMD 中使用环境变量
#### Q: 如何在 CMD 中使用环境变量
```docker
# 方法1使用 shell 格式
## 方法1使用 shell 格式
CMD echo "Port is $PORT"
# 方法2显式使用 sh -c
## 方法2显式使用 sh -c
CMD ["sh", "-c", "echo Port is $PORT"]
```
### Q: 为什么我的容器不响应 Ctrl+C
#### Q: 为什么我的容器不响应 Ctrl+C
可能是使用了 shell 格式信号被 sh 吃掉了
```docker
# ❌ 信号无法传递
## ❌ 信号无法传递
CMD python app.py
# ✅ 信号正确传递
## ✅ 信号正确传递
CMD ["python", "app.py"]
```
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -261,8 +261,8 @@ CMD ["python", "app.py"]
| ** ENTRYPOINT** | CMD 作为 ENTRYPOINT 的默认参数 |
| **核心原则** | 应用必须在前台运行 |
## 延伸阅读
### 延伸阅读
- [ENTRYPOINT 入口点](entrypoint.md)固定的启动命令
- [后台运行](../../05_container/daemon.md)容器前台/后台概念
- [最佳实践](../../15_appendix/best_practices.md)Dockerfile 编写指南
- [后台运行](../../05_container/5.2_daemon.md)容器前台/后台概念
- [最佳实践](../../15_appendix/15.1_best_practices.md)Dockerfile 编写指南

View File

@@ -1,6 +1,6 @@
# COPY 复制文件
## COPY 复制文件
## 基本语法
### 基本语法
```docker
COPY [选项] <源路径>... <目标路径>
@@ -11,33 +11,33 @@ COPY [选项] ["<源路径1>", "<源路径2>", ... "<目标路径>"]
---
## 基本用法
### 基本用法
### 复制单个文件
#### 复制单个文件
```docker
# 复制文件到指定目录
## 复制文件到指定目录
COPY package.json /app/
# 复制文件并重命名
## 复制文件并重命名
COPY config.json /app/settings.json
```
### 复制多个文件
#### 复制多个文件
```docker
# 复制多个指定文件
## 复制多个指定文件
COPY package.json package-lock.json /app/
# 使用通配符
## 使用通配符
COPY *.json /app/
COPY src/*.js /app/src/
```
### 复制目录
#### 复制目录
```docker
# 复制整个目录的内容(不是目录本身)
## 复制整个目录的内容(不是目录本身)
COPY src/ /app/src/
```
@@ -52,7 +52,7 @@ src/ /app/src/
---
## 通配符规则
### 通配符规则
COPY 支持 Go `filepath.Match` 通配符规则
@@ -71,15 +71,15 @@ COPY app[0-9].js /app/ # app0.js ~ app9.js
---
## 目标路径
### 目标路径
### 绝对路径
#### 绝对路径
```docker
COPY app.js /usr/src/app/
```
### 相对路径基于 WORKDIR
#### 相对路径基于 WORKDIR
```docker
WORKDIR /app
@@ -87,29 +87,29 @@ COPY package.json ./ # 复制到 /app/package.json
COPY src/ ./src/ # 复制到 /app/src/
```
### 自动创建目录
#### 自动创建目录
如果目标目录不存在Docker 会自动创建
```docker
# /app/config/ 不存在也会自动创建
## /app/config/ 不存在也会自动创建
COPY settings.json /app/config/
```
---
## 修改文件所有者
### 修改文件所有者
使用 `--chown` 选项设置文件的用户和组
```docker
# 使用用户名和组名
## 使用用户名和组名
COPY --chown=node:node package.json /app/
# 使用 UID 和 GID
## 使用 UID 和 GID
COPY --chown=1000:1000 . /app/
# 只指定用户
## 只指定用户
COPY --chown=node . /app/
```
@@ -117,7 +117,7 @@ COPY --chown=node . /app/
---
## 保留文件元数据
### 保留文件元数据
COPY 会保留源文件的元数据
- 执行权限
@@ -126,13 +126,13 @@ COPY 会保留源文件的元数据:
这对于脚本文件特别重要
```docker
# start.sh 的可执行权限会被保留
## start.sh 的可执行权限会被保留
COPY start.sh /app/
```
---
## COPY vs ADD
### COPY vs ADD
| 特性 | COPY | ADD |
|------|------|-----|
@@ -142,11 +142,11 @@ COPY start.sh /app/
| 推荐程度 | **推荐** | 特殊场景使用 |
```docker
# 推荐:使用 COPY
## 推荐:使用 COPY
COPY app.tar.gz /app/
RUN tar -xzf /app/app.tar.gz
# ADD 会自动解压(行为不明显,不推荐)
## ADD 会自动解压(行为不明显,不推荐)
ADD app.tar.gz /app/
```
@@ -154,12 +154,12 @@ ADD app.tar.gz /app/
---
## 多阶段构建中的 COPY
### 多阶段构建中的 COPY
### 从其他构建阶段复制
#### 从其他构建阶段复制
```docker
# 构建阶段
## 构建阶段
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
@@ -167,15 +167,15 @@ RUN npm install
COPY . .
RUN npm run build
# 生产阶段
## 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
```
### 使用 --link 优化缓存BuildKit
#### 使用 --link 优化缓存BuildKit
```docker
# 使用 --link 后,文件以独立层添加,不依赖前序指令
## 使用 --link 后,文件以独立层添加,不依赖前序指令
COPY --link --from=builder /app/dist /usr/share/nginx/html
```
@@ -186,12 +186,12 @@ COPY --link --from=builder /app/dist /usr/share/nginx/html
---
## .dockerignore
### .dockerignore
使用 `.dockerignore` 排除不需要复制的文件
```gitignore
# .dockerignore
## .dockerignore
node_modules
.git
.env
@@ -207,43 +207,43 @@ Dockerfile
---
## 最佳实践
### 最佳实践
### 1. 利用缓存先复制依赖文件
#### 1. 利用缓存先复制依赖文件
```docker
# ✅ 好:先复制依赖定义,再安装,最后复制代码
## ✅ 好:先复制依赖定义,再安装,最后复制代码
COPY package.json package-lock.json ./
RUN npm install
COPY . .
# ❌ 差:一次性复制所有文件,代码变更会导致重新 npm install
## ❌ 差:一次性复制所有文件,代码变更会导致重新 npm install
COPY . .
RUN npm install
```
### 2. 使用 .dockerignore
#### 2. 使用 .dockerignore
```docker
# 确保 node_modules 不被复制
## 确保 node_modules 不被复制
COPY . .
# .dockerignore 中应包含 node_modules
## .dockerignore 中应包含 node_modules
```
### 3. 明确复制路径
#### 3. 明确复制路径
```docker
# ✅ 好:明确的路径
## ✅ 好:明确的路径
COPY src/ /app/src/
COPY package.json /app/
# ❌ 差:过于宽泛
## ❌ 差:过于宽泛
COPY . .
```
---
## 本章小结
### 本章小结
| 操作 | 示例 |
|------|------|
@@ -253,9 +253,9 @@ COPY . .
| 修改所有者 | `COPY --chown=node:node . /app/` |
| 从构建阶段复制 | `COPY --from=builder /app/dist ./` |
## 延伸阅读
### 延伸阅读
- [ADD 指令](add.md)复制和解压
- [WORKDIR 指令](workdir.md)设置工作目录
- [多阶段构建](../multistage-builds.md)优化镜像大小
- [最佳实践](../../15_appendix/best_practices.md)Dockerfile 编写指南
- [最佳实践](../../15_appendix/15.1_best_practices.md)Dockerfile 编写指南

View File

@@ -1,6 +1,6 @@
# ENTRYPOINT 入口点
## ENTRYPOINT 入口点
## 什么是 ENTRYPOINT
### 什么是 ENTRYPOINT
`ENTRYPOINT` 指定容器启动时运行的入口程序 CMD 不同ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖而是**接收这些参数**
@@ -8,7 +8,7 @@
---
## 语法格式
### 语法格式
| 格式 | 语法 | 推荐程度 |
|------|------|---------|
@@ -16,18 +16,18 @@
| **shell 格式** | `ENTRYPOINT 命令 参数` | 不推荐 |
```docker
# exec 格式(推荐)
## exec 格式(推荐)
ENTRYPOINT ["nginx", "-g", "daemon off;"]
# shell 格式(不推荐)
## shell 格式(不推荐)
ENTRYPOINT nginx -g "daemon off;"
```
---
## ENTRYPOINT vs CMD
### ENTRYPOINT vs CMD
### 核心区别
#### 核心区别
| 特性 | ENTRYPOINT | CMD |
|------|------------|-----|
@@ -36,10 +36,10 @@ ENTRYPOINT nginx -g "daemon off;"
| **覆盖方式** | `--entrypoint` | 直接指定命令 |
| **适用场景** | 把镜像当命令用 | 提供默认行为 |
### 行为对比
#### 行为对比
```docker
# 只用 CMD
## 只用 CMD
CMD ["curl", "-s", "http://example.com"]
```
@@ -50,7 +50,7 @@ $ docker run myimage curl -v ... # curl -v ...(完全替换)
```
```docker
# 只用 ENTRYPOINT
## 只用 ENTRYPOINT
ENTRYPOINT ["curl", "-s"]
```
@@ -60,7 +60,7 @@ $ docker run myimage http://example.com # curl -s http://example.com ✓
```
```docker
# ENTRYPOINT + CMD 组合(推荐)
## ENTRYPOINT + CMD 组合(推荐)
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
```
@@ -73,13 +73,13 @@ $ docker run myimage -v http://other.com # curl -s -v http://other.com ✓
---
## 场景一让镜像像命令一样使用
### 场景一让镜像像命令一样使用
### 需求
#### 需求
创建一个查询公网 IP "命令"镜像
### 使用 CMD 的问题
#### 使用 CMD 的问题
```docker
FROM ubuntu:24.04
@@ -93,10 +93,10 @@ $ docker run myip # ✓ 正常工作
$ docker run myip -i # ✗ 错误!
exec: "-i": executable file not found
# -i 替换了整个 CMD被当作可执行文件
## -i 替换了整个 CMD被当作可执行文件
```
### 使用 ENTRYPOINT 解决
#### 使用 ENTRYPOINT 解决
```docker
FROM ubuntu:24.04
@@ -114,7 +114,7 @@ HTTP/1.1 200 OK
当前 IP61.148.226.66
```
### 交互图示
#### 交互图示
```
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
@@ -129,13 +129,13 @@ curl -s http://myip.ipip.net -i
---
## 场景二启动前的准备工作
### 场景二启动前的准备工作
### 需求
#### 需求
在启动主服务前执行初始化脚本如数据库迁移权限设置
### 实现方式
#### 实现方式
```docker
FROM redis:7-alpine
@@ -150,20 +150,20 @@ CMD ["redis-server"]
#!/bin/sh
set -e
# 准备工作
## 准备工作
echo "Initializing..."
# 如果第一个参数是 redis-server以 redis 用户运行
## 如果第一个参数是 redis-server以 redis 用户运行
if [ "$1" = 'redis-server' ]; then
chown -R redis:redis /data
exec gosu redis "$@"
fi
# 其他命令直接执行
## 其他命令直接执行
exec "$@"
```
### 工作流程
#### 工作流程
```
docker run redis docker run redis bash
@@ -177,7 +177,7 @@ docker-entrypoint.sh redis-server docker-entrypoint.sh bash
(以 redis 用户运行) (以 root 用户运行)
```
### 关键点
#### 关键点
1. **exec "$@"**用传入的参数替换当前进程确保信号正确传递
2. **条件判断**根据 CMD 不同执行不同逻辑
@@ -185,7 +185,7 @@ docker-entrypoint.sh redis-server docker-entrypoint.sh bash
---
## 场景三带参数的应用
### 场景三带参数的应用
```docker
FROM python:3.12-slim
@@ -198,39 +198,39 @@ CMD ["--host", "0.0.0.0", "--port", "8080"]
```
```bash
# 使用默认参数
## 使用默认参数
$ docker run myapp
# 执行: python app.py --host 0.0.0.0 --port 8080
## 执行: python app.py --host 0.0.0.0 --port 8080
# 覆盖参数
## 覆盖参数
$ docker run myapp --host 0.0.0.0 --port 9000
# 执行: python app.py --host 0.0.0.0 --port 9000
## 执行: python app.py --host 0.0.0.0 --port 9000
# 完全不同的参数
## 完全不同的参数
$ docker run myapp --help
# 执行: python app.py --help
## 执行: python app.py --help
```
---
## 覆盖 ENTRYPOINT
### 覆盖 ENTRYPOINT
使用 `--entrypoint` 参数覆盖
```bash
# 正常运行
## 正常运行
$ docker run myimage
# 覆盖 ENTRYPOINT 进入 shell 调试
## 覆盖 ENTRYPOINT 进入 shell 调试
$ docker run --entrypoint /bin/sh myimage
# 覆盖 ENTRYPOINT 并传入参数
## 覆盖 ENTRYPOINT 并传入参数
$ docker run --entrypoint /bin/cat myimage /etc/os-release
```
---
## ENTRYPOINT CMD 组合表
### ENTRYPOINT CMD 组合表
| ENTRYPOINT | CMD | 最终执行命令 |
|------------|-----|-------------|
@@ -244,36 +244,36 @@ $ docker run --entrypoint /bin/cat myimage /etc/os-release
---
## 最佳实践
### 最佳实践
### 1. 使用 exec 格式
#### 1. 使用 exec 格式
```docker
# ✅ 推荐
## ✅ 推荐
ENTRYPOINT ["python", "app.py"]
# ❌ 避免 shell 格式
## ❌ 避免 shell 格式
ENTRYPOINT python app.py
```
### 2. 提供有意义的默认参数
#### 2. 提供有意义的默认参数
```docker
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
```
### 3. 入口脚本使用 exec
#### 3. 入口脚本使用 exec
```bash
#!/bin/sh
# 准备工作...
## 准备工作...
# 使用 exec 替换当前进程
## 使用 exec 替换当前进程
exec "$@"
```
### 4. 处理信号
#### 4. 处理信号
确保 ENTRYPOINT 脚本能正确传递信号
@@ -281,17 +281,17 @@ exec "$@"
#!/bin/bash
trap 'kill -TERM $PID' TERM INT
# 启动应用
## 启动应用
app "$@" &
PID=$!
# 等待应用退出
## 等待应用退出
wait $PID
```
---
## 本章小结
### 本章小结
| ENTRYPOINT | CMD | 适用场景 |
|------------|-----|---------|
@@ -299,8 +299,8 @@ wait $PID
| | | 简单的默认命令 |
| | | **推荐**固定命令 + 可配置参数 |
## 延伸阅读
### 延伸阅读
- [CMD 容器启动命令](cmd.md)默认命令
- [最佳实践](../../15_appendix/best_practices.md)启动命令设计
- [后台运行](../../05_container/daemon.md)前台/后台概念
- [最佳实践](../../15_appendix/15.1_best_practices.md)启动命令设计
- [后台运行](../../05_container/5.2_daemon.md)前台/后台概念

View File

@@ -1,27 +1,27 @@
# ENV 设置环境变量
## ENV 设置环境变量
## 基本语法
### 基本语法
```docker
# 格式一:单个变量
## 格式一:单个变量
ENV <key> <value>
# 格式二:多个变量(推荐)
## 格式二:多个变量(推荐)
ENV <key1>=<value1> <key2>=<value2> ...
```
---
## 基本用法
### 基本用法
### 设置单个变量
#### 设置单个变量
```docker
ENV NODE_VERSION 20.10.0
ENV APP_ENV production
```
### 设置多个变量
#### 设置多个变量
```docker
ENV NODE_VERSION=20.10.0 \
@@ -33,26 +33,26 @@ ENV NODE_VERSION=20.10.0 \
---
## 环境变量的作用
### 环境变量的作用
### 1. 后续指令中使用
#### 1. 后续指令中使用
```docker
ENV NODE_VERSION=20.10.0
# 在 RUN 中使用
## 在 RUN 中使用
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz \
| tar -xJ -C /usr/local --strip-components=1
# 在 WORKDIR 中使用
## 在 WORKDIR 中使用
ENV APP_HOME=/app
WORKDIR $APP_HOME
# 在 COPY 中使用
## 在 COPY 中使用
COPY . $APP_HOME
```
### 2. 容器运行时使用
#### 2. 容器运行时使用
```docker
ENV DATABASE_URL=postgres://localhost/mydb
@@ -71,7 +71,7 @@ const dbUrl = process.env.DATABASE_URL;
---
## 支持环境变量的指令
### 支持环境变量的指令
以下指令可以使用 `$变量名` `${变量名}` 格式
@@ -91,25 +91,25 @@ const dbUrl = process.env.DATABASE_URL;
---
## 运行时覆盖
### 运行时覆盖
使用 `-e` `--env` 覆盖 Dockerfile 中定义的环境变量
```bash
# 覆盖单个变量
## 覆盖单个变量
$ docker run -e APP_ENV=development myimage
# 覆盖多个变量
## 覆盖多个变量
$ docker run -e APP_ENV=development -e DEBUG=true myimage
# 从环境变量文件读取
## 从环境变量文件读取
$ docker run --env-file .env myimage
```
### .env 文件格式
#### .env 文件格式
```bash
# .env
## .env
APP_ENV=development
DEBUG=true
DATABASE_URL=postgres://localhost/mydb
@@ -117,7 +117,7 @@ DATABASE_URL=postgres://localhost/mydb
---
## ENV vs ARG
### ENV vs ARG
| 特性 | ENV | ARG |
|------|-----|-----|
@@ -126,53 +126,53 @@ DATABASE_URL=postgres://localhost/mydb
| **覆盖方式** | `docker run -e` | `docker build --build-arg` |
| **适用场景** | 应用配置 | 构建参数如版本号 |
### 组合使用
#### 组合使用
```docker
# ARG 接收构建时参数
## ARG 接收构建时参数
ARG NODE_VERSION=20
# ENV 保存到运行时
## ENV 保存到运行时
ENV NODE_VERSION=$NODE_VERSION
# 后续指令使用
## 后续指令使用
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/...
```
```bash
# 构建时指定版本
## 构建时指定版本
$ docker build --build-arg NODE_VERSION=18 -t myapp .
```
---
## 最佳实践
### 最佳实践
### 1. 统一管理版本号
#### 1. 统一管理版本号
```docker
# ✅ 好:版本集中管理
## ✅ 好:版本集中管理
ENV NGINX_VERSION=1.25.0 \
NODE_VERSION=20.10.0 \
PYTHON_VERSION=3.12.0
RUN apt-get install nginx=${NGINX_VERSION}
# ❌ 差:版本分散在各处
## ❌ 差:版本分散在各处
RUN apt-get install nginx=1.25.0
```
### 2. 不要存储敏感信息
#### 2. 不要存储敏感信息
```docker
# ❌ 错误:密码写入镜像
## ❌ 错误:密码写入镜像
ENV DB_PASSWORD=secret123
# ✅ 正确:运行时传入
# docker run -e DB_PASSWORD=xxx myimage
## ✅ 正确:运行时传入
## docker run -e DB_PASSWORD=xxx myimage
```
### 3. 为应用提供合理默认值
#### 3. 为应用提供合理默认值
```docker
ENV APP_ENV=production \
@@ -180,50 +180,50 @@ ENV APP_ENV=production \
LOG_LEVEL=info
```
### 4. 使用有意义的变量名
#### 4. 使用有意义的变量名
```docker
# ✅ 好:清晰的命名
## ✅ 好:清晰的命名
ENV REDIS_HOST=localhost \
REDIS_PORT=6379
# ❌ 差:模糊的命名
## ❌ 差:模糊的命名
ENV HOST=localhost \
PORT=6379
```
---
## 常见问题
### 常见问题
### Q: 环境变量在 CMD 中不展开
#### Q: 环境变量在 CMD 中不展开
exec 格式不会自动展开环境变量
```docker
# ❌ 不会展开 $PORT
## ❌ 不会展开 $PORT
CMD ["python", "app.py", "--port", "$PORT"]
# ✅ 使用 shell 格式或显式调用 sh
## ✅ 使用 shell 格式或显式调用 sh
CMD ["sh", "-c", "python app.py --port $PORT"]
```
### Q: 如何查看容器的环境变量
#### Q: 如何查看容器的环境变量
```bash
$ docker inspect mycontainer --format '{{json .Config.Env}}'
$ docker exec mycontainer env
```
### Q: 多行 ENV 还是多个 ENV
#### Q: 多行 ENV 还是多个 ENV
```docker
# ✅ 推荐:减少层数
## ✅ 推荐:减少层数
ENV VAR1=value1 \
VAR2=value2 \
VAR3=value3
# ⚠️ 多个 ENV 会创建多层
## ⚠️ 多个 ENV 会创建多层
ENV VAR1=value1
ENV VAR2=value2
ENV VAR3=value3
@@ -231,7 +231,7 @@ ENV VAR3=value3
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -241,8 +241,8 @@ ENV VAR3=value3
| ** ARG** | ARG 仅构建时ENV 持久化到运行时 |
| **安全** | 不要存储敏感信息 |
## 延伸阅读
### 延伸阅读
- [ARG 构建参数](arg.md)构建时变量
- [Compose 环境变量](../../compose/compose_file.md)Compose 中的环境变量
- [最佳实践](../../15_appendix/best_practices.md)Dockerfile 编写指南
- [Compose 环境变量](../../compose/9.5_compose_file.md)Compose 中的环境变量
- [最佳实践](../../15_appendix/15.1_best_practices.md)Dockerfile 编写指南

View File

@@ -1,6 +1,6 @@
# EXPOSE 声明端口
## EXPOSE 声明端口
## 基本语法
### 基本语法
```docker
EXPOSE <端口> [<端口>/<协议>...]
@@ -10,45 +10,45 @@ EXPOSE <端口> [<端口>/<协议>...]
---
## 基本用法
### 基本用法
```docker
# 声明单个端口
## 声明单个端口
EXPOSE 80
# 声明多个端口
## 声明多个端口
EXPOSE 80 443
# 声明 TCP 和 UDP 端口
## 声明 TCP 和 UDP 端口
EXPOSE 80/tcp
EXPOSE 53/udp
```
---
## EXPOSE 的作用
### EXPOSE 的作用
### 1. 文档说明
#### 1. 文档说明
告诉镜像使用者容器将在哪些端口提供服务
```docker
# 使用者一看就知道这是 web 应用
## 使用者一看就知道这是 web 应用
EXPOSE 80 443
```
```bash
# 查看镜像暴露的端口
## 查看镜像暴露的端口
$ docker inspect nginx --format '{{.Config.ExposedPorts}}'
map[80/tcp:{}]
```
### 2. 配合 -P 使用
#### 2. 配合 -P 使用
使用 `docker run -P` Docker 会自动映射 EXPOSE 的端口到宿主机随机端口
```docker
# Dockerfile
## Dockerfile
EXPOSE 80
```
@@ -60,7 +60,7 @@ $ docker port $(docker ps -q)
---
## EXPOSE vs -p
### EXPOSE vs -p
| 特性 | EXPOSE | -p |
|------|--------|-----|
@@ -85,27 +85,27 @@ $ docker port $(docker ps -q)
└────────────────────────────────────────────────────────────┘
```
### 没有 EXPOSE 也能 -p
#### 没有 EXPOSE 也能 -p
```docker
# 即使没有 EXPOSE也可以使用 -p
## 即使没有 EXPOSE也可以使用 -p
FROM nginx
# 没有 EXPOSE
## 没有 EXPOSE
```
```bash
# 仍然可以映射端口
## 仍然可以映射端口
$ docker run -p 8080:80 mynginx
```
---
## 常见误解
### 常见误解
### 误解EXPOSE 会打开端口
#### 误解EXPOSE 会打开端口
```docker
# ❌ 错误理解:这不会让容器可从外部访问
## ❌ 错误理解:这不会让容器可从外部访问
EXPOSE 80
```
@@ -116,68 +116,68 @@ EXPOSE 不会:
EXPOSE 只是元数据声明容器是否实际监听该端口取决于容器内的应用
### 正确理解
#### 正确理解
```docker
# Dockerfile
## Dockerfile
FROM nginx
EXPOSE 80 # 1. 声明:这个容器会在 80 端口提供服务
```
```bash
# 运行:需要 -p 才能从外部访问
## 运行:需要 -p 才能从外部访问
$ docker run -p 8080:80 nginx # 2. 映射:宿主机 8080 → 容器 80
```
---
## 最佳实践
### 最佳实践
### 1. 总是声明应用使用的端口
#### 1. 总是声明应用使用的端口
```docker
# Web 服务
## Web 服务
FROM nginx
EXPOSE 80 443
# 数据库
## 数据库
FROM postgres
EXPOSE 5432
# Redis
## Redis
FROM redis
EXPOSE 6379
```
### 2. 使用明确的协议
#### 2. 使用明确的协议
```docker
# 默认是 TCP
## 默认是 TCP
EXPOSE 80
# 明确指定 UDP
## 明确指定 UDP
EXPOSE 53/udp
# 同时支持 TCP 和 UDP
## 同时支持 TCP 和 UDP
EXPOSE 53/tcp 53/udp
```
### 3. 与应用实际端口保持一致
#### 3. 与应用实际端口保持一致
```docker
# ✅ 好EXPOSE 与应用端口一致
## ✅ 好EXPOSE 与应用端口一致
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]
# ❌ 差EXPOSE 与应用端口不一致(误导)
## ❌ 差EXPOSE 与应用端口不一致(误导)
EXPOSE 80
CMD ["node", "server.js"] # 实际监听 3000
```
---
## 使用环境变量
### 使用环境变量
```docker
ARG PORT=80
@@ -186,7 +186,7 @@ EXPOSE $PORT
---
## Compose
### Compose
```yaml
services:
@@ -202,7 +202,7 @@ services:
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -212,8 +212,8 @@ services:
| **外部访问** | 需要 `-p 宿主机端口:容器端口` |
| **语法** | `EXPOSE 80` `EXPOSE 80/tcp` |
## 延伸阅读
### 延伸阅读
- [网络配置](../../network/README.md)Docker 网络详解
- [端口映射](../../network/port_bindingbindingbinding.md)-p 参数详解
- [Compose 端口](../../compose/compose_file.md)Compose 中的端口配置
- [Compose 端口](../../compose/9.5_compose_file.md)Compose 中的端口配置

View File

@@ -1,6 +1,6 @@
# HEALTHCHECK 健康检查
## HEALTHCHECK 健康检查
## 基本语法
### 基本语法
```docker
HEALTHCHECK [选项] CMD <命令>
@@ -11,7 +11,7 @@ HEALTHCHECK NONE
---
## 为什么需要 HEALTHCHECK
### 为什么需要 HEALTHCHECK
在没有 HEALTHCHECK 之前Docker 只能通过**进程退出码**来判断容器状态
@@ -32,9 +32,9 @@ Starting ──成功──> Healthy ──失败N次──> Unhealthy
---
## 基本用法
### 基本用法
### Web 服务检查
#### Web 服务检查
```docker
FROM nginx
@@ -44,13 +44,13 @@ HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -fs http://localhost/ || exit 1
```
### 命令返回值
#### 命令返回值
- `0`: 成功 (healthy)
- `1`: 失败 (unhealthy)
- `2`: 保留值 (不使用)
### 常用选项
#### 常用选项
| 选项 | 说明 | 默认值 |
|------|------|--------|
@@ -61,7 +61,7 @@ HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
---
## 屏蔽健康检查
### 屏蔽健康检查
如果基础镜像定义了 HEALTHCHECK但你不想使用它
@@ -72,31 +72,31 @@ HEALTHCHECK NONE
---
## 常见检查脚本
### 常见检查脚本
### HTTP 服务
#### HTTP 服务
使用 `curl` `wget`
```docker
# 使用 curl
## 使用 curl
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
# 使用 wget (Alpine 默认包含)
## 使用 wget (Alpine 默认包含)
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
```
### 数据库
#### 数据库
```docker
# MySQL
## MySQL
HEALTHCHECK CMD mysqladmin ping -h localhost || exit 1
# Redis
## Redis
HEALTHCHECK CMD redis-cli ping || exit 1
```
### 自定义脚本
#### 自定义脚本
```docker
COPY healthcheck.sh /usr/local/bin/
@@ -105,7 +105,7 @@ HEALTHCHECK CMD ["healthcheck.sh"]
---
## Compose 中使用
### Compose 中使用
可以在 `docker-compose.yml` 中覆盖或定义健康检查
@@ -137,16 +137,16 @@ services:
---
## 查看健康状态
### 查看健康状态
```bash
# 查看容器状态(包含健康信息)
## 查看容器状态(包含健康信息)
$ docker ps
CONTAINER ID STATUS
abc123 Up 1 minute (healthy)
def456 Up 2 minutes (unhealthy)
# 查看详细健康日志
## 查看详细健康日志
$ docker inspect --format '{{json .State.Health}}' mycontainer | jq
{
"Status": "healthy",
@@ -164,32 +164,32 @@ $ docker inspect --format '{{json .State.Health}}' mycontainer | jq
---
## 最佳实践
### 最佳实践
### 1. 避免副作用
#### 1. 避免副作用
健康检查会被频繁执行不要在检查脚本中进行写操作或消耗大量资源的操作
### 2. 使用轻量级工具
#### 2. 使用轻量级工具
优先使用镜像中已有的工具 `wget`避免为了健康检查安装庞大的依赖 `curl`
### 3. 设置合理的 Start Period
#### 3. 设置合理的 Start Period
应用启动可能需要时间 Java 应用设置 `--start-period` 可以防止在启动阶段因检查失败而误判
```docker
# 给应用 1 分钟启动时间
## 给应用 1 分钟启动时间
HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
```
### 4. 只检查核心依赖
#### 4. 只检查核心依赖
健康检查应主要关注**当前服务**是否可用而不是检查其下游依赖数据库等下游依赖的检查应由应用逻辑处理
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -199,8 +199,8 @@ HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
| **Compose** | 支持 `condition: service_healthy` 依赖 |
| **注意** | 避免副作用节省资源 |
## 延伸阅读
### 延伸阅读
- [CMD 容器启动命令](cmd.md)启动主进程
- [Compose 模板文件](../../compose/compose_file.md)Compose 中的健康检查
- [Docker 调试](../../15_appendix/debug.md)容器排障
- [Compose 模板文件](../../compose/9.5_compose_file.md)Compose 中的健康检查
- [Docker 调试](../../15_appendix/15.2_debug.md)容器排障

View File

@@ -1,6 +1,6 @@
# LABEL 为镜像添加元数据
## LABEL 为镜像添加元数据
## 基本语法
### 基本语法
```docker
LABEL <key>=<value> <key>=<value> ...
@@ -10,7 +10,7 @@ LABEL <key>=<value> <key>=<value> ...
---
## 为什么需要 LABEL
### 为什么需要 LABEL
1. **版本管理**记录版本号构建时间Git Commit ID
2. **联系信息**维护者邮箱文档地址支持渠道
@@ -19,16 +19,16 @@ LABEL <key>=<value> <key>=<value> ...
---
## 基本用法
### 基本用法
### 定义单个标签
#### 定义单个标签
```docker
LABEL version="1.0"
LABEL description="这是一个 Web 应用服务器"
```
### 定义多个标签推荐
#### 定义多个标签推荐
```docker
LABEL maintainer="user@example.com" \
@@ -41,7 +41,7 @@ LABEL maintainer="user@example.com" \
---
## 常用标签规范 (OCI Annotations)
### 常用标签规范 (OCI Annotations)
为了标准和互操作性推荐使用 [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys) 定义的标准标签:
@@ -57,7 +57,7 @@ LABEL maintainer="user@example.com" \
| `org.opencontainers.image.title` | 镜像标题 | `My App` |
| `org.opencontainers.image.description` | 描述 | `Production ready web server` |
### 示例
#### 示例
```docker
LABEL org.opencontainers.image.authors="yeasy" \
@@ -68,27 +68,27 @@ LABEL org.opencontainers.image.authors="yeasy" \
---
## MAINTAINER 指令已废弃
### MAINTAINER 指令已废弃
旧版本的 Dockerfile 中常看到 `MAINTAINER` 指令
```docker
# ❌ 已弃用
## ❌ 已弃用
MAINTAINER user@example.com
```
现在推荐使用 `LABEL`
```docker
# ✅ 推荐
## ✅ 推荐
LABEL maintainer="user@example.com"
# 或
##
LABEL org.opencontainers.image.authors="user@example.com"
```
---
## 动态标签
### 动态标签
配合 `ARG` 使用可以在构建时动态注入标签
@@ -111,9 +111,9 @@ $ docker build \
---
## 查看标签
### 查看标签
### docker inspect
#### docker inspect
查看镜像的标签信息
@@ -124,21 +124,21 @@ $ docker inspect nginx --format '{{json .Config.Labels}}' | jq
}
```
### 过滤器
#### 过滤器
可以使用标签过滤镜像
```bash
# 列出作者是 yeasy 的所有镜像
## 列出作者是 yeasy 的所有镜像
$ docker images --filter "label=org.opencontainers.image.authors=yeasy"
# 删除所有带有特定标签的镜像
## 删除所有带有特定标签的镜像
$ docker rmi $(docker images -q --filter "label=stage=builder")
```
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -148,7 +148,7 @@ $ docker rmi $(docker images -q --filter "label=stage=builder")
| **弃用** | 不要再使用 `MAINTAINER` |
| **查看** | `docker inspect` |
## 延伸阅读
### 延伸阅读
- [OCI 标签规范](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md)
- [Dockerfile 最佳实践](../../15_appendix/15.1_best_practices.md)

View File

@@ -1,6 +1,6 @@
# ONBUILD 为他人做嫁衣裳
## ONBUILD 为他人做嫁衣裳
## 基本语法
### 基本语法
```docker
ONBUILD <其它指令>
@@ -10,11 +10,11 @@ ONBUILD <其它指令>
---
## 为什么需要 ONBUILD
### 为什么需要 ONBUILD
`ONBUILD` 主要用于制作**语言栈基础镜像****框架基础镜像**
### 场景维护 Node.js 项目
#### 场景维护 Node.js 项目
假设你有多个 Node.js 项目它们的构建流程都一样
1. 创建目录
@@ -25,7 +25,7 @@ ONBUILD <其它指令>
如果不使用 `ONBUILD`每个项目的 Dockerfile 都要重复这些步骤且通过 `COPY` 复制文件时基础镜像无法预知子项目的文件名
### 使用 ONBUILD 的解决方案
#### 使用 ONBUILD 的解决方案
**基础镜像 (my-node-base)**
@@ -33,7 +33,7 @@ ONBUILD <其它指令>
FROM node:20-alpine
WORKDIR /app
# 这些指令将在子镜像构建时执行
## 这些指令将在子镜像构建时执行
ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .
@@ -45,13 +45,13 @@ CMD ["npm", "start"]
```docker
FROM my-node-base
# 只需要一行!
# 构建时会自动执行 COPY 和 RUN
## 只需要一行!
## 构建时会自动执行 COPY 和 RUN
```
---
## 执行机制
### 执行机制
```
基础镜像构建:
@@ -64,59 +64,59 @@ FROM 基础镜像 ──build──> 读取基础镜像触发器 ──> 执行
---
## 常见使用场景
### 常见使用场景
### 1. 自动处理依赖安装
#### 1. 自动处理依赖安装
```docker
# Python 基础镜像
## Python 基础镜像
ONBUILD COPY requirements.txt ./
ONBUILD RUN pip install -r requirements.txt
```
### 2. 自动编译代码
#### 2. 自动编译代码
```docker
# Go 基础镜像
## Go 基础镜像
ONBUILD COPY . .
ONBUILD RUN go build -o app main.go
```
### 3. 处理静态资源
#### 3. 处理静态资源
```docker
# Nginx 静态网站基础镜像
## Nginx 静态网站基础镜像
ONBUILD COPY dist/ /usr/share/nginx/html/
```
---
## 注意事项
### 注意事项
### 1. 继承性限制
#### 1. 继承性限制
`ONBUILD` 指令**只会继承一次**
- 镜像 A ( ONBUILD)
- 镜像 B (FROM A) -> 触发 ONBUILD
- 镜像 C (FROM B) -> **不会**再次触发 ONBUILD
### 2. 构建上下文
#### 2. 构建上下文
子镜像构建时`ONBUILD COPY . .` 中的 `.` 指的是**子项目**的构建上下文而不是基础镜像的上下文
### 3. 不允许级联
#### 3. 不允许级联
`ONBUILD ONBUILD` 是非法的你不能写 `ONBUILD ONBUILD COPY ...`
### 4. 可能会导致构建失败
#### 4. 可能会导致构建失败
由于 `ONBUILD` 实际上是在子镜像中执行指令如果子项目的上下文不满足要求例如缺少 `package.json`会导致子镜像构建失败且错误信息可能比较隐晦
---
## 最佳实践
### 最佳实践
### 1. 命名规范
#### 1. 命名规范
建议在镜像标签中添加 `-onbuild` 后缀明确告知使用者该镜像包含触发器
@@ -125,17 +125,17 @@ node:20-onbuild
python:3.12-onbuild
```
### 2. 避免执行耗时操作
#### 2. 避免执行耗时操作
尽量不要在 `ONBUILD` 中执行过于耗时或不确定的操作如更新系统软件这会让子镜像构建变得缓慢且不可控
### 3. 清理工作
#### 3. 清理工作
如果 `ONBUILD` 指令产生了临时文件最好在同一个指令链中清理或者提供机制让子镜像清理
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -145,7 +145,7 @@ python:3.12-onbuild
| **限制** | 只继承一次不可级联 |
| **规范** | 建议使用 `-onbuild` 标签后缀 |
## 延伸阅读
### 延伸阅读
- [COPY 指令](copy.md)文件复制
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md)基础镜像设计
- [Dockerfile 最佳实践](../../15_appendix/15.1_best_practices.md)基础镜像设计

View File

@@ -1,4 +1,4 @@
# 参考文档
## 参考文档
* `Dockerfile` 官方文档https://docs.docker.com/engine/reference/builder/

View File

@@ -1,6 +1,6 @@
# RUN 执行命令
## RUN 执行命令
## 基本语法
### 基本语法
```docker
RUN <command>
@@ -11,9 +11,9 @@ RUN ["executable", "param1", "param2"]
---
## 两种格式对比
### 两种格式对比
### 1. Shell 格式
#### 1. Shell 格式
```docker
RUN apt-get update
@@ -26,7 +26,7 @@ RUN apt-get update
RUN echo "Hello" > /test.txt
```
### 2. Exec 格式
#### 2. Exec 格式
```docker
RUN ["apt-get", "update"]
@@ -38,9 +38,9 @@ RUN ["apt-get", "update"]
---
## 常见最佳实践
### 常见最佳实践
### 1. 组合命令减少层数
#### 1. 组合命令减少层数
每一个 `RUN` 指令都会新建一层镜像为了减少镜像体积和层数应使用 `&&` 连接命令
@@ -60,7 +60,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
```
### 2. 清理缓存
#### 2. 清理缓存
在安装完软件后立即清除缓存可以显著减小镜像体积
@@ -74,14 +74,14 @@ RUN apt-get update && \
RUN apk add --no-cache package-bar
```
### 3. 使用 `set -e` `pipefail`
#### 3. 使用 `set -e` `pipefail`
默认情况下管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功整个 `RUN` 就视为成功
** 隐蔽的错误**
```docker
# 如果下载失败gzip 可能会报错,但如果不影响后续,构建可能继续
## 如果下载失败gzip 可能会报错,但如果不影响后续,构建可能继续
RUN wget http://error-url | gzip -d > file
```
@@ -94,9 +94,9 @@ RUN wget http://url | gzip -d > file
---
## 常见问题
### 常见问题
### Q: 为什么 `RUN cd /app` 不生效
#### Q: 为什么 `RUN cd /app` 不生效
```docker
RUN cd /app
@@ -114,7 +114,7 @@ WORKDIR /app
RUN touch hello.txt
```
### Q: 环境变量不生效
#### Q: 环境变量不生效
```docker
RUN export MY_VAR=hello
@@ -134,26 +134,26 @@ RUN echo $MY_VAR
---
## 高级技巧
### 高级技巧
### 1. 使用 BuildKit 的挂载缓存
#### 1. 使用 BuildKit 的挂载缓存
BuildKit 支持在 `RUN` 指令中使用 `--mount` 挂载缓存加速构建
```docker
# 缓存 apt 包
## 缓存 apt 包
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y gcc
```
```docker
# 缓存 Go 模块
## 缓存 Go 模块
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o app
```
### 2. 挂载密钥
#### 2. 挂载密钥
安全地使用 SSH 密钥或 Token而不将其记录在镜像中
@@ -164,7 +164,7 @@ RUN --mount=type=secret,id=mysecret \
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -174,8 +174,8 @@ RUN --mount=type=secret,id=mysecret \
| **陷阱** | `cd` 不持久环境变量不持久 |
| **进阶** | 使用 Cache Mount 加速构建 |
## 延伸阅读
### 延伸阅读
- [CMD 容器启动命令](cmd.md)容器启动时的命令
- [WORKDIR 指定工作目录](workdir.md)改变目录
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md)
- [Dockerfile 最佳实践](../../15_appendix/15.1_best_practices.md)

View File

@@ -1,6 +1,6 @@
# SHELL 指令
## SHELL 指令
## 基本语法
### 基本语法
```docker
SHELL ["executable", "parameters"]
@@ -14,83 +14,83 @@ SHELL ["executable", "parameters"]
---
## 为什么要用 SHELL 指令
### 为什么要用 SHELL 指令
### 1. 使用 bash 特性
#### 1. 使用 bash 特性
默认的 `/bin/sh`通常是 dash alpine ash功能有限如果你需要使用 bash 的特有功能如数组`{}` 扩展`pipefail` 可以切换 shell
```docker
FROM ubuntu:24.04
# 切换到 bash
## 切换到 bash
SHELL ["/bin/bash", "-c"]
# 现在可以使用 bash 特性了
## 现在可以使用 bash 特性了
RUN echo {a..z}
```
### 2. 增强错误处理 (pipefail)
#### 2. 增强错误处理 (pipefail)
默认情况下管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功整个指令就视为成功这可能掩盖构建错误
```docker
# ❌ 这里的 wget 失败了,但构建继续(因为 tar 成功了)
## ❌ 这里的 wget 失败了,但构建继续(因为 tar 成功了)
RUN wget -O - https://invalid-url | tar xz
```
使用 `SHELL` 启用 `pipefail`
```docker
# ✅ 启用 pipefail
## ✅ 启用 pipefail
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# 如果 wget 失败,整个 RUN 就会失败
## 如果 wget 失败,整个 RUN 就会失败
RUN wget -O - https://invalid-url | tar xz
```
### 3. Windows 环境
#### 3. Windows 环境
Windows 容器中经常需要在 `cmd` `powershell` 之间切换
```docker
FROM mcr.microsoft.com/windows/servercore:ltsc2022
# 默认是 cmd
## 默认是 cmd
RUN echo Default shell is cmd
# 切换到 powershell
## 切换到 powershell
SHELL ["powershell", "-command"]
RUN Write-Host "Hello from PowerShell"
# 切回 cmd
## 切回 cmd
SHELL ["cmd", "/S", "/C"]
```
---
## 作用范围
### 作用范围
`SHELL` 指令可以出现多次每次只影响其后的指令
```docker
FROM ubuntu:24.04
# 使用默认 sh
## 使用默认 sh
RUN echo "Using sh"
SHELL ["/bin/bash", "-c"]
# 使用 bash
## 使用 bash
RUN echo "Using bash"
SHELL ["/bin/sh", "-c"]
# 回到 sh
## 回到 sh
RUN echo "Using sh again"
```
---
## 对其他指令的影响
### 对其他指令的影响
`SHELL` 影响的是所有使用 **shell 格式** 的指令
@@ -105,9 +105,9 @@ RUN echo "Using sh again"
---
## 最佳实践
### 最佳实践
### 1. 推荐开启 pipefail
#### 1. 推荐开启 pipefail
对于使用 bash 的镜像强烈建议开启 `pipefail`以确保构建过程中的错误能被及时捕获
@@ -115,17 +115,17 @@ RUN echo "Using sh again"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
```
### 2. 明确意图
#### 2. 明确意图
如果由于脚本需求必须更改 shell最好在 Dockerfile 中显式声明而不是依赖默认行为
### 3. 尽量保持一致
#### 3. 尽量保持一致
避免在 Dockerfile 中频繁切换 SHELL这会使构建过程难以理解和调试尽量在头部定义一次即可
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -135,7 +135,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
| **推荐用法** | `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` |
| **影响范围** | 后续所有使用 shell 格式的指令 |
## 延伸阅读
### 延伸阅读
- [RUN 指令](../../04_image/build.md)执行命令
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md)错误处理与调试
- [RUN 指令](../../04_image/4.5_build.md)执行命令
- [Dockerfile 最佳实践](../../15_appendix/15.1_best_practices.md)错误处理与调试

View File

@@ -1,6 +1,6 @@
# USER 指定当前用户
## USER 指定当前用户
## 基本语法
### 基本语法
```docker
USER <用户名>[:<用户组>]
@@ -11,7 +11,7 @@ USER <UID>[:<GID>]
---
## 为什么要使用 USER
### 为什么要使用 USER
> 笔者强调以非 root 用户运行容器是最重要的安全实践之一
@@ -33,52 +33,52 @@ root 用户运行的风险:
---
## 基本用法
### 基本用法
### 创建并切换用户
#### 创建并切换用户
```docker
FROM node:20-alpine
# 1. 创建用户和组
## 1. 创建用户和组
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -D appuser
# 2. 设置目录权限
## 2. 设置目录权限
WORKDIR /app
COPY --chown=appuser:appgroup . .
# 3. 切换用户
## 3. 切换用户
USER appuser
# 4. 后续命令以 appuser 身份运行
## 4. 后续命令以 appuser 身份运行
CMD ["node", "server.js"]
```
### 使用 UID/GID
#### 使用 UID/GID
```docker
# 也可以使用数字
## 也可以使用数字
USER 1001:1001
```
---
## 用户必须已存在
### 用户必须已存在
`USER` 指令只能切换到**已存在**的用户
```docker
# ❌ 错误:用户不存在
## ❌ 错误:用户不存在
USER nonexistent
# Error: unable to find user nonexistent
## Error: unable to find user nonexistent
# ✅ 正确:先创建用户
## ✅ 正确:先创建用户
RUN useradd -r -s /bin/false appuser
USER appuser
```
### 创建用户的方式
#### 创建用户的方式
**Debian/Ubuntu**
@@ -104,19 +104,19 @@ RUN addgroup -g 1001 -S appgroup && \
---
## 运行时切换用户
### 运行时切换用户
### 使用 gosu推荐
#### 使用 gosu推荐
ENTRYPOINT 脚本中切换用户时不要使用 `su` `sudo`应使用 [gosu](https://github.com/tianon/gosu)
```docker
FROM debian:bookworm
# 创建用户
## 创建用户
RUN groupadd -r redis && useradd -r -g redis redis
# 安装 gosu
## 安装 gosu
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
COPY docker-entrypoint.sh /usr/local/bin/
@@ -130,14 +130,14 @@ CMD ["redis-server"]
#!/bin/bash
set -e
# 以 root 执行初始化
## 以 root 执行初始化
chown -R redis:redis /data
# 用 gosu 切换到 redis 用户运行服务
## 用 gosu 切换到 redis 用户运行服务
exec gosu redis "$@"
```
### 为什么不用 su/sudo
#### 为什么不用 su/sudo
| 问题 | su/sudo | gosu |
|------|---------|------|
@@ -148,38 +148,38 @@ exec gosu redis "$@"
---
## 运行时覆盖用户
### 运行时覆盖用户
使用 `-u` `--user` 参数
```bash
# 以指定用户运行
## 以指定用户运行
$ docker run -u 1001:1001 myimage
# 以 root 运行(调试时)
## 以 root 运行(调试时)
$ docker run -u root myimage
```
---
## 文件权限处理
### 文件权限处理
切换用户后确保应用有权访问文件
```docker
FROM node:20-alpine
# 创建用户
## 创建用户
RUN adduser -D -u 1001 appuser
WORKDIR /app
# 方式1使用 --chown
## 方式1使用 --chown
COPY --chown=appuser:appuser . .
# 方式2手动 chown减少层数
# COPY . .
# RUN chown -R appuser:appuser /app
## 方式2手动 chown减少层数
## COPY . .
## RUN chown -R appuser:appuser /app
USER appuser
CMD ["node", "server.js"]
@@ -187,41 +187,41 @@ CMD ["node", "server.js"]
---
## 最佳实践
### 最佳实践
### 1. 始终使用非 root 用户
#### 1. 始终使用非 root 用户
```docker
# ✅ 推荐
## ✅ 推荐
RUN adduser -D appuser
USER appuser
CMD ["myapp"]
# ❌ 避免
## ❌ 避免
CMD ["myapp"] # 以 root 运行
```
### 2. 使用固定 UID/GID
#### 2. 使用固定 UID/GID
便于在宿主机和容器间共享文件
```docker
# 使用常见的非 root UID
## 使用常见的非 root UID
RUN addgroup -g 1000 -S appgroup && \
adduser -u 1000 -S -G appgroup appuser
USER 1000:1000
```
### 3. 多阶段构建中的 USER
#### 3. 多阶段构建中的 USER
```docker
# 构建阶段可以用 root
## 构建阶段可以用 root
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
# 生产阶段用非 root
## 生产阶段用非 root
FROM node:20-alpine
RUN adduser -D appuser
WORKDIR /app
@@ -232,9 +232,9 @@ CMD ["node", "server.js"]
---
## 常见问题
### 常见问题
### Q: 权限被拒绝
#### Q: 权限被拒绝
```bash
permission denied: '/app/data.log'
@@ -246,7 +246,7 @@ permission denied: '/app/data.log'
RUN mkdir -p /app/data && chown appuser:appuser /app/data
```
### Q: 无法绑定低于 1024 的端口
#### Q: 无法绑定低于 1024 的端口
root 用户无法绑定 80443 等端口
@@ -256,7 +256,7 @@ RUN mkdir -p /app/data && chown appuser:appuser /app/data
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -266,8 +266,8 @@ RUN mkdir -p /app/data && chown appuser:appuser /app/data
| **运行时覆盖** | `docker run -u` |
| **切换工具** | 使用 gosu不用 su/sudo |
## 延伸阅读
### 延伸阅读
- [安全](../../security/README.md)容器安全实践
- [ENTRYPOINT](entrypoint.md)入口脚本中的用户切换
- [最佳实践](../../15_appendix/best_practices.md)Dockerfile 安全
- [最佳实践](../../15_appendix/15.1_best_practices.md)Dockerfile 安全

View File

@@ -1,6 +1,6 @@
# VOLUME 定义匿名卷
## VOLUME 定义匿名卷
## 基本语法
### 基本语法
```docker
VOLUME ["/路径1", "/路径2"]
@@ -11,7 +11,7 @@ VOLUME /路径
---
## 为什么使用 VOLUME
### 为什么使用 VOLUME
> **核心原则**容器存储层应该保持无状态任何运行时数据都应该存储在卷中
@@ -34,16 +34,16 @@ VOLUME /路径
---
## 基本用法
### 基本用法
### 定义单个卷
#### 定义单个卷
```docker
FROM mysql:8.0
VOLUME /var/lib/mysql
```
### 定义多个卷
#### 定义多个卷
```docker
FROM myapp
@@ -52,9 +52,9 @@ VOLUME ["/data", "/logs", "/config"]
---
## VOLUME 的行为
### VOLUME 的行为
### 1. 自动创建匿名卷
#### 1. 自动创建匿名卷
如果运行时未指定挂载Docker 会自动创建匿名卷
@@ -65,23 +65,23 @@ DRIVER VOLUME NAME
local a1b2c3d4e5f6... # 自动创建的匿名卷
```
### 2. 可被命名卷覆盖
#### 2. 可被命名卷覆盖
```bash
# 使用命名卷替代匿名卷
## 使用命名卷替代匿名卷
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
```
### 3. 可被 Bind Mount 覆盖
#### 3. 可被 Bind Mount 覆盖
```bash
# 使用宿主机目录替代
## 使用宿主机目录替代
$ docker run -v /my/data:/var/lib/mysql mysql:8.0
```
---
## VOLUME 在构建时的特殊行为
### VOLUME 在构建时的特殊行为
> **重要**VOLUME 之后对该目录的修改会被丢弃
@@ -89,43 +89,43 @@ $ docker run -v /my/data:/var/lib/mysql mysql:8.0
FROM ubuntu
VOLUME /data
# ❌ 这个文件不会出现在镜像中!
## ❌ 这个文件不会出现在镜像中!
RUN echo "hello" > /data/test.txt
```
**原因**VOLUME 指令之后Docker 将该目录视为外部挂载点不再记录对它的修改
### 正确做法
#### 正确做法
```docker
FROM ubuntu
# ✅ 先写入文件
## ✅ 先写入文件
RUN mkdir -p /data && echo "hello" > /data/test.txt
# 再声明 VOLUME
## 再声明 VOLUME
VOLUME /data
```
---
## 常见使用场景
### 常见使用场景
### 数据库持久化
#### 数据库持久化
```docker
FROM postgres:15
VOLUME /var/lib/postgresql/data
```
### 日志目录
#### 日志目录
```docker
FROM nginx
VOLUME /var/log/nginx
```
### 上传文件目录
#### 上传文件目录
```docker
FROM myapp
@@ -134,22 +134,22 @@ VOLUME /app/uploads
---
## 查看 VOLUME 定义
### 查看 VOLUME 定义
```bash
# 查看镜像定义的 VOLUME
## 查看镜像定义的 VOLUME
$ docker inspect mysql:8.0 --format '{{json .Config.Volumes}}' | jq
{
"/var/lib/mysql": {}
}
# 查看容器挂载的卷
## 查看容器挂载的卷
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
```
---
## VOLUME vs docker run -v
### VOLUME vs docker run -v
| 特性 | Dockerfile VOLUME | docker run -v |
|------|-------------------|---------------|
@@ -160,7 +160,7 @@ $ docker inspect mycontainer --format '{{json .Mounts}}' | jq
---
## Compose
### Compose
```yaml
services:
@@ -178,14 +178,14 @@ volumes:
---
## 安全注意事项
### 安全注意事项
### 匿名卷可能导致数据丢失
#### 匿名卷可能导致数据丢失
```bash
# 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
## 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
$ docker run --rm mysql:8.0
# 容器停止后,数据丢失!
## 容器停止后,数据丢失!
```
**解决**始终使用命名卷
@@ -196,41 +196,41 @@ $ docker run -v mysql_data:/var/lib/mysql mysql:8.0
---
## 最佳实践
### 最佳实践
### 1. 定义必须持久化的路径
#### 1. 定义必须持久化的路径
```docker
# 数据库必须使用卷
## 数据库必须使用卷
FROM postgres:15
VOLUME /var/lib/postgresql/data
```
### 2. 不要在 VOLUME 后修改目录
#### 2. 不要在 VOLUME 后修改目录
```docker
# ❌ 避免
## ❌ 避免
VOLUME /app/data
RUN cp init-data.json /app/data/
# ✅ 正确
## ✅ 正确
RUN mkdir -p /app/data && cp init-data.json /app/data/
VOLUME /app/data
```
### 3. 文档中说明 VOLUME 用途
#### 3. 文档中说明 VOLUME 用途
```docker
# 持久化用户上传的文件
## 持久化用户上传的文件
VOLUME /app/uploads
# 持久化数据库数据
## 持久化数据库数据
VOLUME /var/lib/mysql
```
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -240,8 +240,8 @@ VOLUME /var/lib/mysql
| **覆盖方式** | `docker run -v name:/path` |
| **注意** | VOLUME 之后的修改会丢失 |
## 延伸阅读
### 延伸阅读
- [数据卷](../../07_data_network/data/volume.md)卷的管理和使用
- [挂载主机目录](../../07_data_network/data/bind-mounts.md)Bind Mount
- [Compose 数据管理](../../compose/compose_file.md)Compose 中的卷配置
- [Compose 数据管理](../../compose/9.5_compose_file.md)Compose 中的卷配置

View File

@@ -1,6 +1,6 @@
# WORKDIR 指定工作目录
## WORKDIR 指定工作目录
## 基本语法
### 基本语法
```docker
WORKDIR <工作目录路径>
@@ -10,7 +10,7 @@ WORKDIR <工作目录路径>
---
## 基本用法
### 基本用法
```docker
WORKDIR /app
@@ -22,17 +22,17 @@ COPY . . # 复制到 /app/
---
## 为什么需要 WORKDIR
### 为什么需要 WORKDIR
### 常见错误
#### 常见错误
```docker
# ❌ 错误cd 在下一个 RUN 中无效
## ❌ 错误cd 在下一个 RUN 中无效
RUN cd /app
RUN echo "hello" > world.txt # 文件在根目录!
```
### 原因分析
#### 原因分析
```
RUN cd /app
@@ -47,17 +47,17 @@ RUN echo "hello" > world.txt
每个 RUN 都在新容器中执行**前一个 RUN 的内存状态包括工作目录不会保留**
### 正确做法
#### 正确做法
```docker
# ✅ 正确:使用 WORKDIR
## ✅ 正确:使用 WORKDIR
WORKDIR /app
RUN echo "hello" > world.txt # 创建 /app/world.txt
```
---
## 相对路径
### 相对路径
WORKDIR 支持相对路径基于上一个 WORKDIR
@@ -71,7 +71,7 @@ RUN pwd # 输出 /a/b/c
---
## 使用环境变量
### 使用环境变量
```docker
ENV APP_HOME=/app
@@ -82,10 +82,10 @@ RUN pwd # 输出 /app
---
## 多阶段构建中的 WORKDIR
### 多阶段构建中的 WORKDIR
```docker
# 构建阶段
## 构建阶段
FROM node:20 AS builder
WORKDIR /build
COPY package*.json ./
@@ -93,7 +93,7 @@ RUN npm install
COPY . .
RUN npm run build
# 生产阶段
## 生产阶段
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY --from=builder /build/dist .
@@ -101,9 +101,9 @@ COPY --from=builder /build/dist .
---
## 最佳实践
### 最佳实践
### 1. 尽早设置 WORKDIR
#### 1. 尽早设置 WORKDIR
```docker
FROM node:20
@@ -115,40 +115,40 @@ COPY . .
CMD ["node", "server.js"]
```
### 2. 使用绝对路径
#### 2. 使用绝对路径
```docker
# ✅ 推荐:绝对路径,意图明确
## ✅ 推荐:绝对路径,意图明确
WORKDIR /app
# ⚠️ 避免:相对路径可能造成混淆
## ⚠️ 避免:相对路径可能造成混淆
WORKDIR app
```
### 3. 不要用 RUN cd
#### 3. 不要用 RUN cd
```docker
# ❌ 避免
## ❌ 避免
RUN cd /app && echo "hello" > world.txt
# ✅ 推荐
## ✅ 推荐
WORKDIR /app
RUN echo "hello" > world.txt
```
### 4. 适时重置 WORKDIR
#### 4. 适时重置 WORKDIR
```docker
WORKDIR /app
# ... 应用相关操作 ...
## ... 应用相关操作 ...
WORKDIR /data
# ... 数据相关操作 ...
## ... 数据相关操作 ...
```
---
## 与其他指令的关系
### 与其他指令的关系
| 指令 | WORKDIR 的影响 |
|------|---------------|
@@ -168,7 +168,7 @@ CMD ["./start.sh"] # /app/start.sh
---
## 运行时覆盖
### 运行时覆盖
使用 `-w` 参数覆盖工作目录
@@ -179,7 +179,7 @@ $ docker run -w /tmp myimage pwd
---
## 本章小结
### 本章小结
| 要点 | 说明 |
|------|------|
@@ -189,8 +189,8 @@ $ docker run -w /tmp myimage pwd
| **持久性** | 影响后续所有指令直到下次 WORKDIR |
| **不要用** | `RUN cd /path`无效 |
## 延伸阅读
### 延伸阅读
- [COPY 复制文件](copy.md)文件复制
- [RUN 执行命令](../../04_image/build.md)执行构建命令
- [最佳实践](../../15_appendix/best_practices.md)Dockerfile 编写指南
- [RUN 执行命令](../../04_image/4.5_build.md)执行构建命令
- [最佳实践](../../15_appendix/15.1_best_practices.md)Dockerfile 编写指南

View File

@@ -1,10 +1,10 @@
# 多阶段构建
## 多阶段构建
## 之前的做法
### 之前的做法
Docker 17.05 版本之前我们构建 Docker 镜像时通常会采用两种方式
### 全部放入一个 Dockerfile
#### 全部放入一个 Dockerfile
一种方式是将所有的构建过程编包含在一个 `Dockerfile` 包括项目及其依赖库的编译测试打包等流程这里可能会带来的一些问题
@@ -51,7 +51,7 @@ CMD ["./app"]
$ docker build -t go/helloworld:1 -f Dockerfile.one .
```
### 分散到多个 Dockerfile
#### 分散到多个 Dockerfile
另一种方式就是我们事先在一个 `Dockerfile` 将项目及其依赖库编译测试打包好后再将其拷贝到运行环境中这种方式需要我们编写两个 `Dockerfile` 和一些编译脚本才能将其两个阶段自动整合起来这种方式虽然可以很好地规避第一种方式存在的风险但明显部署过程较复杂
@@ -120,7 +120,7 @@ go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
```
## 使用多阶段构建
### 使用多阶段构建
为解决以上问题Docker v17.05 开始支持多阶段构建 (`multistage builds`)使用多阶段构建我们就可以很容易解决前面提到的问题并且只需要编写一个 `Dockerfile`
@@ -169,7 +169,7 @@ go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
很明显使用多阶段构建的镜像体积小同时也完美解决了上边提到的问题
### 只构建某一阶段的镜像
#### 只构建某一阶段的镜像
我们可以使用 `as` 来为某一阶段命名例如
@@ -183,7 +183,7 @@ FROM golang:alpine as builder
$ docker build --target builder -t username/imagename:tag .
```
### 构建时从其他镜像复制文件
#### 构建时从其他镜像复制文件
上面例子中我们使用 `COPY --from=0 /go/src/github.com/go/helloworld/app .` 从上一阶段的镜像中复制文件我们也可以复制任意镜像中的文件

View File

@@ -1,8 +1,8 @@
# 实战多阶段构建 Laravel 镜像
## 实战多阶段构建 Laravel 镜像
> 本节适用于 PHP 开发者阅读`Laravel` 基于 8.x 版本各个版本的文件结构可能会有差异请根据实际自行修改
## 准备
### 准备
新建一个 `Laravel` 项目或在已有的 `Laravel` 项目根目录下新建 `Dockerfile` `.dockerignore` `laravel.conf` 文件
@@ -25,7 +25,7 @@ yarn-error.log
bootstrap/cache/*
storage/
# 自行添加其他需要排除的文件,例如 .env.* 文件
## 自行添加其他需要排除的文件,例如 .env.* 文件
```
`laravel.conf` 文件中写入 nginx 配置
@@ -51,7 +51,7 @@ server {
}
```
## 前端构建
### 前端构建
第一阶段进行前端构建
@@ -72,7 +72,7 @@ RUN set -x ; cd /app \
&& npm run production
```
## 安装 Composer 依赖
### 安装 Composer 依赖
第二阶段安装 Composer 依赖
@@ -92,7 +92,7 @@ RUN set -x ; cd /app \
--prefer-dist
```
## 整合以上阶段所生成的文件
### 整合以上阶段所生成的文件
第三阶段对以上阶段生成的文件进行整合
@@ -118,7 +118,7 @@ RUN set -x ; cd ${LARAVEL_PATH} \
&& php artisan package:discover
```
## 最后一个阶段构建 NGINX 镜像
### 最后一个阶段构建 NGINX 镜像
```docker
FROM nginx:alpine as nginx
@@ -129,7 +129,7 @@ COPY laravel.conf /etc/nginx/conf.d/
COPY --from=laravel ${LARAVEL_PATH}/public ${LARAVEL_PATH}/public
```
## 构建 Laravel Nginx 镜像
### 构建 Laravel Nginx 镜像
使用 `docker build` 命令构建镜像
@@ -139,7 +139,7 @@ $ docker build -t my/laravel --target=laravel .
$ docker build -t my/nginx --target=nginx .
```
## 启动容器并测试
### 启动容器并测试
新建 Docker 网络
@@ -163,13 +163,13 @@ $ docker run -dit --rm --network=laravel -p 8080:80 my/nginx
> 也许 Laravel 项目依赖其他外部服务例如 redisMySQL请自行启动这些服务之后再进行测试本小节不再赘述
## 生产环境优化
### 生产环境优化
本小节内容为了方便测试将配置文件直接放到了镜像中实际在使用时 **建议** 将配置文件作为 `config` `secret` 挂载到容器中请读者自行学习 `Kubernetes` 的相关内容
由于篇幅所限本小节只是简单列出更多内容可以参考 https://github.com/khs1994-docker/laravel-demo 项目。
## 附录
### 附录
完整的 `Dockerfile` 文件如下