mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-13 13:21:18 +00:00
Use a better structure
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
# Dockerfile 指令详解
|
||||
## Dockerfile 指令详解
|
||||
|
||||
我们已经介绍了 `FROM`,`RUN`,还提及了 `COPY`, `ADD`,其实 `Dockerfile` 功能很强大,它提供了十多个指令。下面我们继续讲解其他的指令。
|
||||
|
||||
@@ -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 编写指南
|
||||
|
||||
@@ -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):复杂构建场景
|
||||
|
||||
@@ -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 编写指南
|
||||
|
||||
@@ -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 编写指南
|
||||
|
||||
@@ -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
|
||||
当前 IP:61.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):前台/后台概念
|
||||
|
||||
@@ -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 编写指南
|
||||
|
||||
@@ -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 中的端口配置
|
||||
|
||||
@@ -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):容器排障
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):基础镜像设计
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 参考文档
|
||||
## 参考文档
|
||||
|
||||
* `Dockerfile` 官方文档:https://docs.docker.com/engine/reference/builder/
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):错误处理与调试
|
||||
|
||||
@@ -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 用户无法绑定 80、443 等端口。
|
||||
|
||||
@@ -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 安全
|
||||
|
||||
@@ -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 中的卷配置
|
||||
|
||||
@@ -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 编写指南
|
||||
|
||||
Reference in New Issue
Block a user