Docker 镜像与 Dockerfile
Docker 镜像保存文件系统层与运行配置。本文负责 Dockerfile、构建缓存和数据挂载。
1. 面试核心
先记住这几条,再读正文- 镜像与容器。 镜像保存只读层与元数据,容器另有独立可写层。卡 · 分层与构建缓存
- 启动命令。 exec 格式的
ENTRYPOINT指定程序,CMD提供默认参数。卡 · 常用指令 - 多阶段构建。 多阶段构建只向运行阶段复制所需产物。卡 · 体积与构建速度优化
- 数据持久化。 卷的生命周期独立于容器,可保存持久数据。卡 · 分层与构建缓存
- 构建密钥。 BuildKit secret 挂载按构建步骤提供密钥。卡 · 镜像标识与密钥
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 和后续删除的文件都会留下暴露风险。密钥变化不会自动使缓存失效。需要重新执行时,应显式调整缓存输入。