mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-08-10 08:27:25 +00:00
201 lines
4.6 KiB
Go
201 lines
4.6 KiB
Go
## 7.1 RUN 执行命令
|
||
|
||
### 何时使用 RUN
|
||
|
||
`RUN` 是 Dockerfile 中最常用的指令,主要用于在镜像构建阶段执行命令来修改镜像。具体来说:
|
||
|
||
- **安装依赖**:`RUN apt-get install nginx`
|
||
- **编译程序**:`RUN gcc -o app main.c`
|
||
- **下载文件**:`RUN curl -O https://example.com/file.tar.gz`
|
||
- **配置系统**:`RUN mkdir -p /app/data`
|
||
|
||
理解 RUN 的核心是理解**镜像分层**:每一个 RUN 都会在当前层之上创建新的一层,这会影响镜像大小。因此,合理使用 RUN(特别是合并多个 RUN)是构建轻量级镜像的关键。
|
||
|
||
### 7.1.1 基本语法
|
||
|
||
```docker
|
||
RUN <command>
|
||
RUN ["executable", "param1", "param2"]
|
||
```
|
||
`RUN` 指令是 Dockerfile 中最常用的指令之一。它在 **当前镜像层** 之上创建一个新层,执行指定的命令,并提交结果。
|
||
|
||
---
|
||
|
||
### 7.1.2 两种格式对比
|
||
|
||
#### 1. Shell 格式
|
||
|
||
```docker
|
||
RUN apt-get update
|
||
```
|
||
|
||
- **特点**:默认通过 `/bin/sh -c` 执行。
|
||
- **优势**:可以使用环境变量、管道、重定向等 Shell 特性。
|
||
- **示例**:
|
||
```docker
|
||
RUN echo "Hello" > /test.txt
|
||
```
|
||
|
||
#### 2. Exec 格式
|
||
|
||
```docker
|
||
RUN ["apt-get", "update"]
|
||
```
|
||
|
||
- **特点**:直接调用可执行文件,不经过 Shell。
|
||
- **优势**:避免 Shell 字符串解析问题,适用于参数中包含特殊字符的情况。
|
||
- **注意**:无法使用 `$VAR` 环境变量替换 (除非显式调用 shell)。
|
||
|
||
---
|
||
|
||
### 7.1.3 常见最佳实践
|
||
|
||
#### 1. 组合命令:减少层数
|
||
|
||
每一个 `RUN` 指令都会新建一层镜像。为了减少镜像体积和层数,应使用 `&&` 连接命令。
|
||
|
||
**❌ 糟糕的写法** (创建 3 层):
|
||
|
||
```docker
|
||
RUN apt-get update
|
||
RUN apt-get install -y nginx
|
||
RUN rm -rf /var/lib/apt/lists/*
|
||
```
|
||
**✅ 推荐写法** (创建 1 层):
|
||
|
||
```docker
|
||
RUN apt-get update && \
|
||
apt-get install -y nginx && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
```
|
||
|
||
#### 2. 清理缓存
|
||
|
||
在安装完软件后,立即清除缓存,可以显著减小镜像体积。
|
||
|
||
- **Debian/Ubuntu**:
|
||
```docker
|
||
RUN apt-get update && apt-get install -y package-bar \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
```
|
||
|
||
- **Alpine**:
|
||
```docker
|
||
RUN apk add --no-cache package-bar
|
||
```
|
||
|
||
#### 3. 使用 `set -e` 和 `pipefail`
|
||
|
||
默认情况下,管道命令 `cmd1 | cmd2` 的返回值由最后一个命令 (`cmd2`) 决定,即使前面的命令失败了,整个 `RUN` 仍可能视为成功。
|
||
|
||
**❌ 隐蔽的错误**:
|
||
|
||
```docker
|
||
## 如果下载失败,gzip 可能会报错,但如果不影响后续,构建可能继续
|
||
|
||
RUN wget http://error-url | gzip -d > file
|
||
```
|
||
**✅ 推荐写法**:
|
||
|
||
```docker
|
||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||
RUN wget http://url | gzip -d > file
|
||
```
|
||
---
|
||
|
||
### 7.1.4 常见问题
|
||
|
||
#### Q:为什么 `RUN cd /app` 不生效?
|
||
|
||
```docker
|
||
RUN cd /app
|
||
RUN touch hello.txt
|
||
```
|
||
**结果**:`hello.txt` 会出现在根目录 `/`,而不是 `/app`。**原因**:每个 `RUN` 都在一个新的 Shell/容器环境中执行。`cd` 只影响当前 `RUN` 的环境。**解决**:使用 `WORKDIR` 指令。
|
||
|
||
```docker
|
||
WORKDIR /app
|
||
RUN touch hello.txt
|
||
```
|
||
|
||
#### Q:环境变量不生效?
|
||
|
||
```docker
|
||
RUN export MY_VAR=hello
|
||
RUN echo $MY_VAR
|
||
```
|
||
**结果**:输出为空。**原因**:同上,环境变量只在当前 `RUN` 有效。**解决**:使用 `ENV` 指令,或在同一行 `RUN` 中导出。
|
||
|
||
```docker
|
||
ENV MY_VAR=hello
|
||
RUN echo $MY_VAR
|
||
```
|
||
---
|
||
|
||
### 7.1.5 高级技巧
|
||
|
||
#### 1. 使用 BuildKit 的挂载缓存
|
||
|
||
BuildKit 支持在 `RUN` 指令中使用 `--mount` 挂载缓存,加速构建。
|
||
|
||
```docker
|
||
## 缓存 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 模块
|
||
|
||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||
go build -o app
|
||
```
|
||
|
||
#### 2. 挂载密钥
|
||
|
||
安全地使用 SSH 密钥或 Token,而不将其记录在镜像中。
|
||
|
||
```docker
|
||
RUN --mount=type=secret,id=mysecret \
|
||
cat /run/secrets/mysecret
|
||
```
|
||
|
||
#### 3. Heredoc 语法
|
||
|
||
BuildKit 支持使用 heredoc 语法编写多行脚本,无需行末反斜杠 `\` 连接:
|
||
|
||
```docker
|
||
RUN <<EOF
|
||
apt-get update
|
||
apt-get install -y \
|
||
build-essential \
|
||
curl \
|
||
git
|
||
rm -rf /var/lib/apt/lists/*
|
||
EOF
|
||
```
|
||
|
||
优势:
|
||
|
||
- 可读性更强,不需要在每行末尾添加 `\`
|
||
- 避免在脚本中转义引号
|
||
- 支持多个 heredoc 块,可指定不同的 Shell
|
||
|
||
```docker
|
||
RUN <<EOF
|
||
echo "使用默认 /bin/sh"
|
||
EOF
|
||
|
||
RUN <<'EOF'
|
||
#!/bin/bash
|
||
set -euo pipefail
|
||
echo "使用 bash"
|
||
wget http://example.com/file.tar.gz
|
||
EOF
|
||
```
|
||
|
||
> 使用 heredoc 需要在 Dockerfile 首行声明 BuildKit 语法版本:`# syntax=docker/dockerfile:1`
|
||
|
||
---
|