跳到主要内容
约 6 分钟4 张卡

Docker 镜像与 Dockerfile

Docker 镜像保存文件系统层与运行配置。本文负责 Dockerfile、构建缓存和数据挂载。

1. 面试核心​

先记住这几条,再读正文

2. 常用指令​

下面假设构建上下文已有 target/app.jar。

FROM eclipse-temurin:21-jre
WORKDIR /app
ENV TZ=Asia/Shanghai
COPY target/app.jar app.jar
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--spring.profiles.active=prod"]

容器以 UID 10001 启动 Java。应用需将写入目录授权给该用户。docker run image --server.port=9090 会替换默认参数。

指令行为
FROM指定阶段的基础镜像
RUN在构建阶段执行命令
WORKDIR设置工作目录
COPY复制构建上下文中的文件
COPY --from从其他阶段、镜像或命名上下文复制
ADD另支持远程 URL、Git 和本地 tar 解包
ARG声明构建参数及作用域
ENV将环境变量写入镜像配置
USER设置后续运行使用的用户
EXPOSE声明容器服务端口
HEALTHCHECK声明运行时健康检查命令

CMD 单独使用时可指定默认启动命令。与 exec 格式 ENTRYPOINT 组合时,它提供默认参数。--entrypoint 可替换入口程序。

exec 格式直接启动程序,便于接收停止信号。shell 格式经 /bin/sh -c 执行。入口脚本应使用 exec 交接给业务进程。信号处理与子进程回收见 进程管理与信号。

EXPOSE 记录端口信息。docker run -p 才建立主机端口发布。健康检查命令所用的工具必须存在于镜像中。

3. 分层与构建缓存​

构建缓存根据指令和输入判断能否复用。COPY 的输入改变后,依赖它的后续步骤需要重建。BuildKit 会按依赖图处理多阶段构建。无依赖的阶段可独立使用缓存。

COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

只修改源码时,依赖安装步骤仍可命中缓存。锁文件变化后,npm ci 会重新执行。COPY 的缓存校验忽略单独的文件 mtime 变化。

普通 RUN 缓存不会主动检测远程软件仓库更新。需要更新依赖时,应调整固定版本或显式使缓存失效。docker history <image> 可查看构建历史与大小。

后续层删除早期层的文件,只会遮住旧内容。因此,安装与清理应在同一 RUN 中完成。具体机制见 容器原理。

4. 体积与构建速度优化​

多阶段构建分离编译环境与运行环境。下面假设 Maven 将产物命名为 app.jar。

FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /src
COPY pom.xml .
RUN mvn -q dependency:go-offline
COPY src ./src
RUN mvn -q package -DskipTests

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/target/app.jar app.jar
USER 10001:10001
ENTRYPOINT ["java", "-jar", "app.jar"]

最终阶段只包含 JRE 和应用产物。示例跳过了测试,发布前仍需完成测试。

方法作用与条件
精简基础镜像减少运行依赖;Alpine 的 musl 需做兼容验证
同层清理避免将包管理缓存保留在旧层
.dockerignore排除无关输入,保留构建必需的产物
依赖步骤前置源码变化时复用依赖层
BuildKit cache 挂载在多次构建间复用下载缓存
远程构建缓存在不同 CI 执行器间共享缓存

5. 容器的数据存储​

方式示例生命周期
命名卷--mount type=volume,src=data,dst=/data独立于容器,由 Docker 管理
绑定挂载--mount type=bind,src=/host/data,dst=/data使用宿主路径中的数据
tmpfs--tmpfs /tmp停止容器后数据消失

tmpfs 使用内存,宿主配置允许时也可进入 swap。绑定挂载依赖宿主的目录布局和权限。只读配置可使用 readonly 挂载。

多个容器可挂载同一卷。并发写入的协调由应用或文件系统承担。Docker 挂载本身没有提供业务事务。

Docker Engine 29.0 的全新安装使用 containerd 镜像存储。它与升级后沿用的经典存储布局不同。data-root 管理 Docker 数据目录。containerd 管理的镜像和快照还有独立目录。迁移存储前需确认实际后端。

6. 镜像标识与密钥​

tag 是可移动的名称,digest 按内容定位镜像。FROM image@sha256:... 可固定基础镜像内容。应用依赖及远程下载内容也需固定,才能控制构建输入。

构建密钥应通过 BuildKit secret 挂载提供。下面假设构建阶段已经安装 curl。

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=token,required=true \
curl --config /run/secrets/token https://example.com/artifact -o artifact

构建时用 --secret id=token,src=token.conf 提供配置。挂载只对该步骤可见。命令仍需避免把密钥复制到产物或打印到日志。

ARG 与 ENV 会暴露构建或运行配置。它们不适合传递密钥。secret 内容变化本身也不会使构建缓存失效。需要重新拉取时,应显式调整缓存输入。

7 · 面试题卡片​

本页题目与参考回答

7.1 Dockerfile 有哪些常用指令?CMD 和 ENTRYPOINT、COPY 和 ADD 有什么区别?​

考察重点

  • 常用指令的用途;构建期和运行期指令的区分。
  • CMD 与 ENTRYPOINT 的组合规则、exec 格式与信号;COPY 与 ADD;EXPOSE 只是声明。

相关内容:常用指令。

参考回答

Dockerfile 分别定义构建步骤、运行配置和启动命令;COPY 负责明确的文件复制,ADD 还提供远程来源和解包能力。

  • 构建与配置。 FROM 指定基础镜像,RUN 执行构建命令。WORKDIR、ENV、USER 设置运行配置,EXPOSE 声明端口,HEALTHCHECK 定义检查命令。
  • 启动命令。 exec 格式的 ENTRYPOINT 指定程序,CMD 提供默认参数。运行时参数覆盖 CMD,--entrypoint 可替换入口程序。入口脚本用 exec 将信号接收交给应用。
  • 文件复制。 COPY --from 可从其他阶段或镜像复制。ADD 另支持远程来源和本地 tar 解包;普通复制使用 COPY,输入行为更明确。

7.2 构建镜像时,怎样优化镜像体积和构建速度?​

考察重点

  • 多阶段构建、精简基础镜像、同层清理、.dockerignore。
  • 利用层缓存的指令顺序;删除文件不缩小镜像的原因。

相关内容:体积与构建速度优化、分层与构建缓存。

参考回答

多阶段构建减少运行镜像内容,缓存复用缩短构建时间。

最终阶段只复制产物,使用满足运行要求的基础镜像。安装与清理放在同一步骤,避免旧层保留缓存。

先复制依赖清单,再安装依赖,最后复制源码。.dockerignore 排除无关输入。BuildKit cache 挂载复用依赖下载。CI 还可导入和导出远程缓存。docker history 用于检查优化后的镜像历史。

7.3 镜像和容器有什么区别?镜像为什么要分层?两个容器怎样共享数据?​

考察重点

  • 只读层与可写层、写时复制;分层带来的复用。
  • 卷、绑定挂载与 tmpfs 的区别。

相关内容:分层与构建缓存、容器的数据存储、容器原理。

参考回答

镜像是文件层与配置,容器是镜像的运行实例。

运行时为容器增加独立可写层。停止后的容器仍保留该层,删除容器才移除它。

分层让镜像共享相同内容,拉取时可复用已有层。卷和绑定挂载可让多个容器访问同一份数据。卷独立于容器生命周期,绑定挂载直接使用宿主路径。tmpfs 的内容在容器停止后消失。共享写入仍需要应用协调。

7.4 固定镜像 tag 能保证输入不变吗?构建密钥应怎样传入?​

考察重点:内容摘要、缓存与临时密钥挂载。

相关内容:镜像标识与密钥。

参考回答

tag 可指向新内容,digest 才固定所引用的镜像内容。

完整构建还需固定依赖和下载输入。密钥通过 BuildKit secret 挂载传入。ARG、ENV 和后续删除的文件都会留下暴露风险。密钥变化不会自动使缓存失效。需要重新执行时,应显式调整缓存输入。

8. 参考资料​