07_dockerfile/7.1_run.md
RUN 是 Dockerfile 中最常用的指令,主要用于在镜像构建阶段执行命令来修改镜像。具体来说:
RUN apt-get install nginxRUN gcc -o app main.cRUN curl -O https://example.com/file.tar.gzRUN mkdir -p /app/data理解 RUN 的核心是理解镜像分层:每一个 RUN 都会在当前层之上创建新的一层,这会影响镜像大小。因此,合理使用 RUN(特别是合并多个 RUN)是构建轻量级镜像的关键。
RUN <command>
RUN ["executable", "param1", "param2"]
RUN 指令是 Dockerfile 中最常用的指令之一。它在 当前镜像层 之上创建一个新层,执行指定的命令,并提交结果。
RUN apt-get update
/bin/sh -c 执行。RUN echo "Hello" > /test.txt
RUN ["apt-get", "update"]
$VAR 环境变量替换 (除非显式调用 shell)。每一个 RUN 指令都会新建一层镜像。为了减少镜像体积和层数,应使用 && 连接命令。
❌ 糟糕的写法 (创建 3 层):
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
✅ 推荐写法 (创建 1 层):
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
在安装完软件后,立即清除缓存,可以显著减小镜像体积。
Debian/Ubuntu:
RUN apt-get update && apt-get install -y package-bar \
&& rm -rf /var/lib/apt/lists/*
Alpine:
RUN apk add --no-cache package-bar
set -e 和 pipefail默认情况下,管道命令 cmd1 | cmd2 的返回值由最后一个命令 (cmd2) 决定,即使前面的命令失败了,整个 RUN 仍可能视为成功。
❌ 隐蔽的错误:
## 如果下载失败,gzip 可能会报错,但如果不影响后续,构建可能继续
RUN wget http://error-url | gzip -d > file
✅ 推荐写法:
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wget http://url | gzip -d > file
RUN cd /app 不生效?RUN cd /app
RUN touch hello.txt
结果:hello.txt 会出现在根目录 /,而不是 /app。原因:每个 RUN 都在一个新的 Shell/容器环境中执行。cd 只影响当前 RUN 的环境。解决:使用 WORKDIR 指令。
WORKDIR /app
RUN touch hello.txt
RUN export MY_VAR=hello
RUN echo $MY_VAR
结果:输出为空。原因:同上,环境变量只在当前 RUN 有效。解决:使用 ENV 指令,或在同一行 RUN 中导出。
ENV MY_VAR=hello
RUN echo $MY_VAR
BuildKit 支持在 RUN 指令中使用 --mount 挂载缓存,加速构建。
## 缓存 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
## 缓存 Go 模块
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o app
安全地使用 SSH 密钥或 Token,而不将其记录在镜像中。
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret
BuildKit 支持使用 heredoc 语法编写多行脚本,无需行末反斜杠 \ 连接:
RUN <<EOF
apt-get update
apt-get install -y \
build-essential \
curl \
git
rm -rf /var/lib/apt/lists/*
EOF
优势:
\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