Docker / 命令文档
Dockerfile 指令详解(面试向)
概述
本篇是「Docker 面试核心」系列第三篇,逐条讲 Dockerfile 高频指令:FROM、RUN、COPY、ADD、WORKDIR、ENV、ARG、EXPOSE、CMD、ENTRYPOINT、USER。CMD 与 ENTRYPOINT 的配合、ARG 与 ENV 的区别是每年的必考题。
本篇所有构建与运行输出均在 Docker 26.1.4 测试机实测。示例 Dockerfile:
FROM alpine:3.19
ARG VERSION=1.0
ENV APP_NAME=kb-app VERSION=${VERSION}
WORKDIR /app
COPY hello.sh /app/
RUN chmod +x /app/hello.sh && echo "built ${VERSION}" > /app/build.info
USER nobody
EXPOSE 8080
ENTRYPOINT ["/app/hello.sh"]
CMD ["default-arg"]FROM 与 RUN
FROM 指定基础镜像(一切层的起点),RUN 在构建期执行命令并生成新层。
FROM alpine:3.19 # 建议带版本 Tag,不用 latest(不可复现) RUN chmod +x /app/hello.sh && echo "built" > /app/build.info # 每条 RUN = 一层
面试要点:
- 一条 RUN = 一个镜像层,层只增不减:把
apt update && apt install && rm -rf /var/lib/apt/lists/*写进同一条 RUN 才能减小体积 - 多条 RUN 之间用
&&或 heredoc 合并是瘦身的基本功;RUN cd xxx不改后续层的工作目录,应该用 WORKDIR - FROM 可多次出现实现多阶段构建(见镜像篇);
FROM scratch表示从零开始(Go 静态编译常用)
COPY 与 ADD
COPY 只做「从构建上下文复制文件」,行为可预测;ADD 额外支持自动解压本地 tar 与远程 URL,语义杂、不推荐。
COPY hello.sh /app/ # 推荐:单纯复制 ADD app.tar.gz /opt/ # 会自动解压到 /opt(唯一比 COPY 好用的场景)
面试要点:
- 两者都只能取构建上下文内的文件(上下文外路径直接报错)
- COPY 支持多源与
--chown;源路径改变会使其后所有层缓存失效 - 面试标准答案:优先 COPY,只有「需要自动解 tar」时才用 ADD;远程文件下载应该用 RUN curl/wget(还能与清理合层)
WORKDIR、ENV 与 ARG
WORKDIR 设定后续指令的工作目录(自动创建);ENV 定义运行时也在的环境变量;ARG 只在构建期有效。
# 构建期:--build-arg 覆盖 ARG 默认值,ENV 借 ARG 固化
$ docker build -t kb-test:2.0 --build-arg VERSION=2.0 /tmp/kb_build
# 运行期:ENV 在容器内可见,ARG 不可见(已固化进 ENV 的 VERSION 例外)
$ docker inspect -f "{{range .Config.Env}}{{println .}}{{end}}" kb-test:2.0
APP_NAME=kb-app
VERSION=2.0
PATH=/usr/local/sbin:...
$ docker inspect -f "User={{.Config.User}} WorkDir={{.Config.WorkingDir}}" kb-test:2.0
User=nobody WorkDir=/app面试要点:
- ARG vs ENV 标准答案:ARG 构建期可被 --build-arg 覆盖、不进入容器环境;ENV 写进镜像、运行时可见且可被
docker run -e覆盖 - ARG 变化的层及其后所有层缓存失效 —— 把 ARG 放在尽量靠后的位置保护缓存
- ENV 的最佳用途:路径、JVM 参数这类「运行时需要」的配置;敏感信息(密码)既不该进 ARG 也不该进 ENV(build history 可见)
EXPOSE 与 USER
EXPOSE 只是元数据声明(文档 + docker run -P 随机映射的依据),不做任何端口转发;USER 指定运行身份。
$ docker inspect -f "{{.Config.ExposedPorts}}" kb-test:2.0
map[8080/tcp:{}]
$ docker inspect -f "{{.Config.User}}" kb-test:2.0
nobody面试要点:
- EXPOSE 不等于 -p:真正把端口发布到宿主机的是
docker run -p;EXPOSE 是给人和-P看的声明 - 生产镜像应避免以 root 运行:Dockerfile 末尾
USER app+ 文件属主配合 chown;root 容器被攻破即宿主机风险 - 验证身份:
docker exec <容器> whoami;alpine 里用id -u
CMD 与 ENTRYPOINT(必考)
ENTRYPOINT 是固定的可执行入口,CMD 是默认参数;两者都有时拼成 ENTRYPOINT + CMD。run 命令行传参时 CMD 被替换、ENTRYPOINT 仍在。
实测两种行为:
# 1) ENTRYPOINT + CMD:不传参 → CMD 作为默认参数 $ docker run --rm kb-test:1.0 APP=kb-app VER=1.0 args=default-arg # 2) 传参 → CMD 被覆盖,追加到 ENTRYPOINT 后 $ docker run --rm kb-test:1.0 "cli-arg" APP=kb-app VER=1.0 args=cli-arg # 3) CMD-only 镜像:传参是整体替换 CMD $ docker run --rm kb-cmd 默认输出 $ docker run --rm kb-cmd echo "被替换的输出" 被替换的输出 # 4) --entrypoint 运行时替换入口 $ docker run --rm --entrypoint cat kb-test:2.0 /app/build.info built 2.0
面试要点:
- 推荐写法:
ENTRYPOINT ["exec-form"]固定程序 +CMD ["默认参数"]给用户覆盖空间 - exec form(JSON 数组)不走 shell,信号直达进程(优雅退出正常);shell form(
CMD app)实际是/bin/sh -c app,SIGTERM 打在 sh 上,应用收不到,docker stop只能等超时强杀 - shell form 想正确传信号要在脚本里
exec真实程序(exec form 语义前移)
完整示例与构建缓存
构建缓存逐层命中:没有输入变化的层全部 CACHED;COPY 的源文件或 ARG 的值一变,从那一层开始全部重建。
# 无改动重建:除 FROM 外全部 CACHED $ docker build -t kb-test:1.0 /tmp/kb_build 2>&1 | grep -c CACHED 3 # --build-arg VERSION=2.0 使引用它的 RUN 层失效,其后层全部重建 $ docker build -t kb-test:2.0 --build-arg VERSION=2.0 /tmp/kb_build 2>&1 | tail -3 #6 CACHED #7 CACHED #8 [4/4] RUN chmod +x /app/hello.sh && echo "built 2.0" > /app/build.info
面试要点:
- 缓存失效规则:某层的输入(父层、指令、COPY 的文件、ARG 值)变了,它和它之后所有层失效 —— 所以「不常变的放前面,常变的放后面」
- 依赖安装层单独成层并先 COPY 依赖清单(如 package.json/requirements.txt)再 COPY 代码,代码改动不触发依赖重装
--no-cache或同层指令变化可强制重建;CI 里可用--cache-from复用远端缓存
高频面试题速答
| 问题 | 一句话答案 |
|---|---|
| CMD 与 ENTRYPOINT 区别 | ENTRYPOINT 定程序、CMD 给默认参数;run 传参覆盖 CMD 不动 ENTRYPOINT,两者都有时拼接执行 |
| ARG 与 ENV 区别 | ARG 构建期变量可被 --build-arg 覆盖且不进容器;ENV 固化进镜像、运行时可见可被 -e 覆盖 |
| COPY 与 ADD 区别 | COPY 单纯复制行为可预测;ADD 多了自动解 tar 和远程 URL,优先 COPY |
| EXPOSE 会开放端口吗 | 不会,只是元数据声明;真正发布端口是 docker run -p |
| shell form 有什么坑 | 以 /bin/sh -c 运行,信号被 sh 拦截导致 stop 超时强杀,用 exec form 或脚本内 exec |
| 为什么镜像越构建越大 | 每条 RUN 是独立只读层,删文件不缩小下层,须合并清理到同一层 |
| 构建缓存何时失效 | 父层变化、指令变化、COPY 源文件变化、ARG 值变化,从该层起全部重建 |
| 如何减小镜像体积 | 小基础镜像 + 多阶段构建 + 合层清理包管理器缓存 + .dockerignore |