Dockerfile 指令详解
第 12 章学会了 Dockerfile 骨架,本章把常用指令逐条讲透,重点辨析 CMD 与 ENTRYPOINT、COPY 与 ADD、shell 形式与 exec 形式的差别,让构建文件既正确又可维护。
指令总览
FROM 必须是第一个可执行指令,其余指令汇总如下:
| 指令 | 作用 | 典型写法 |
|---|---|---|
| FROM | 指定基础镜像 | FROM node:20-alpine |
| RUN | 构建期执行命令,每个 RUN 生成一层 | RUN apt-get update && apt-get install -y curl |
| COPY | 把构建上下文内的文件拷入镜像 | COPY package.json /app/ |
| ADD | 类似 COPY,可自动解压 tar、下载远程 URL | ADD jdk-17.tar.gz /opt/ |
| CMD | 启动时默认命令,可被 docker run 覆盖 | CMD ["node", "server.js"] |
| ENTRYPOINT | 固定主程序,配合 CMD 传默认参数 | ENTRYPOINT ["docker-entrypoint.sh"] |
| ENV | 环境变量,构建期与运行期都可见 | ENV NODE_ENV=production |
| ARG | 仅构建期变量,不写入镜像 | ARG APP_VERSION=2.4 |
| EXPOSE | 声明监听端口,仅文档作用,不真开端口 | EXPOSE 8080 |
| WORKDIR | 工作目录,不存在会自动创建 | WORKDIR /app |
| USER | 切换后续指令与运行用户 | USER node |
| VOLUME | 声明匿名卷,便于持久化 | VOLUME /data |
| ONBUILD | 子镜像构建时自动触发的指令 | ONBUILD COPY . /app |
| HEALTHCHECK | 定义容器健康检查命令 | HEALTHCHECK CMD wget -q http://localhost:80/ |
ENV 与 ARG
两者都定义变量,差别在生命周期:ARG 只在构建期存在,构建时用 docker build --build-arg 覆盖;ENV 会写进镜像,容器运行时可读(运行时 echo $APP_HOME 输出 /opt/myapp)。
ARG APP_VERSION=2.4 # 构建期默认值,可被命令行覆盖
ENV APP_HOME=/opt/myapp # 运行期可见;可用 \ 续行一次定义多个
WORKDIR $APP_HOME # 用 $ 引用已定义的变量
CMD 与 ENTRYPOINT
CMD 提供默认命令或参数,会被 docker run 末尾追加的命令整体替换;ENTRYPOINT 定义固定主程序。标准组合是 ENTRYPOINT 写程序、CMD 写默认参数;同一 Dockerfile 出现多个 CMD 只有最后一个生效:
ENTRYPOINT ["nginx", "-g", "daemon off;"]
CMD ["-t"] # 默认参数;docker run 之后可替换成别的参数
docker run nginx-img -t # 实际执行:nginx -g "daemon off;" -t
docker run --entrypoint bash nginx-img -c "echo ok" # 用 --entrypoint 整体替换
COPY 与 ADD
COPY 只做拷贝;ADD 额外支持从远程 URL 下载、自动解压本地 tar 包,行为多、易出意外,官方建议能 COPY 就不用 ADD,自动解压 tar 是它不可替代的场景:
COPY ./conf/nginx.conf /etc/nginx/nginx.conf # 拷单个文件
COPY ./html/ /usr/share/nginx/html/ # 拷整个目录
ADD jdk-17_linux-x64_bin.tar.gz /opt/jdk/ # 本地 tar 自动解压到 /opt/jdk/
RUN 层与构建缓存
每个 RUN 与 COPY 都生成一个只读层,构建时按「指令 + 输入文件内容」逐条比对缓存。把依赖安装放前面、源码拷贝放后面,改源码就不会撞翻整条缓存链;需要干净构建时加 --no-cache:
FROM python:3.12-slim
COPY requirements.txt /app/ # 依赖清单先拷
RUN pip install -r /app/requirements.txt # 清单没变就命中缓存
COPY . /app # 源码变动只影响本层及之后
shell 形式与 exec 形式
命令类指令(RUN/CMD/ENTRYPOINT)有 shell 与 exec 两种写法:shell 形式由 /bin/sh -c 包一层执行;exec 形式是 JSON 数组、必须用双引号,进程直接成为容器 1 号进程:
CMD echo "hello world" # shell 形式
CMD ["echo", "hello world"] # exec 形式
CMD ["sh", "-c", "echo hello $NAME"] # exec 中要展开变量须显式套 sh -c
docker stop 默认发 SIGTERM:exec 形式下信号直达主进程可优雅退出;shell 形式下信号常被 sh 吞掉、只能等超时强杀,生产镜像优先 exec 形式。
综合示例
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
USER node # 以非 root 运行更安全
EXPOSE 3000
CMD ["node", "server.js"]
docker build -t my-node . && docker run -d -p 3000:3000 --name node-app my-node
小结
指令组合是 Dockerfile 的核心功力:FROM 选底、COPY/ADD 组装、RUN 装依赖、ENV/ARG 传参、CMD+ENTRYPOINT 定启动、USER/HEALTHCHECK 定契约;多用 exec 形式与 COPY,缓存和信号问题就少一半。