Back to Docker Practice

7.1 Run

07_dockerfile/7.1_run.md

1.9.04.6 KB
Original Source

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 -epipefail

默认情况下,管道命令 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