Docker 镜像制作笔记
一、镜像制作的原因
当官方镜像无法满足特定需求时,需要自定义镜像,常见原因包括:
- 代码打包
将自编写的代码打包到镜像中,随镜像发布。 - 安全性考虑
第三方镜像可能存在安全漏洞,需要自制镜像。 - 特定功能需求
官方镜像无法满足功能,如数据库审计、特殊配置。 - 公司内部规范
基于公司内部操作系统或标准构建镜像。
二、镜像制作方式
1. 快照方式(偶尔制作的镜像)
- 思路:在基础镜像上启动容器,安装所需软件和配置后,创建快照。
- 命令 :
docker commit - 语法:
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
- 常用参数 :
-a:提交作者-c:使用 Dockerfile 指令创建镜像,可修改启动指令-m:提交说明-p:提交时暂停容器
2. Dockerfile 构建方式(经常更新的镜像)
- 思路 :将安装流程写入 Dockerfile,通过
docker build构建镜像。 - 特点 :
- 可重复构建
- 易于版本管理
- 自动化 CI/CD 支持
三、快照方式实战
实战一:C++ HelloWorld 镜像
- 准备工作目录:
mkdir -p /data/maxhou/commitimage
cd /data/maxhou/commitimage
- 创建 C++ 源码:
#include <stdio.h>
int main() {
printf("hello docker!\n");
return 0;
}
- 启动 CentOS 容器:
docker run -it --name mycppcommit centos:7 bash
- 替换国内软件源(CentOS 7 已停止更新):
sed -i.bak \
-e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos\|baseurl=https://mirrors.ustc.edu.cn/centos-vault/centos\|g' \
/etc/yum.repos.d/CentOS-Base.repo
yum makecache
- 安装编译器并创建源代码目录:
yum install -y gcc
mkdir /src/
- 拷贝源码到容器:
docker cp ./demo.c mycppcommit:/src
- 编译运行测试:
cd /src
gcc demo.c -o demo
./demo # 输出 hello docker!
- 提交为镜像:
docker commit mycppcommit mycppimg:v1.0
docker images
- 运行镜像验证:
docker run -it mycppimg:v1.0 ./src/demo
一、Dockerfile 是什么
👉 本质:
- Dockerfile = 镜像构建脚本
- 用来描述"镜像每一层怎么生成"
👉 核心思想:
镜像 = 一层一层叠加
Dockerfile = 每一层的构建指令
📌 格式
注释
INSTRUCTION arguments
特点:
- 指令不区分大小写(但必须大写规范写法)
- 从上到下顺序执行
- 每条指令 = 一层镜像
🧠 类比理解
👉 Dockerfile 就像:
🏠 建房子的施工图纸
- FROM → 地基
- RUN → 施工
- COPY → 搬材料
- CMD → 房子怎么用
二、为什么用 Dockerfile(比 commit 强在哪)
1️⃣ 可定制
- 自定义环境 + 代码 + 配置
2️⃣ 自动化
- 一键 build,不用手动敲命令
3️⃣ 可重复
- 同一个 Dockerfile → 构建结果一致
4️⃣ 非黑箱(重点)
- commit = 黑箱 ❌
- Dockerfile = 可读可维护 ✅
5️⃣ 镜像更小
- 可清理缓存
- 支持多阶段构建(高级重点)
三、核心指令(必须掌握)
1️⃣ FROM(基础镜像)
👉 作用:
- 指定基于哪个镜像构建
FROM ubuntu:22.04
📌 特点:
- 必须是第一条指令(除 ARG 外)
- 默认 tag = latest
- 可多次使用(多阶段构建)
2️⃣ LABEL(推荐替代 MAINTAINER)
LABEL company="com.bit" app="nginx"
👉 作用:
- 添加元数据(key-value)
3️⃣ COPY(重点)
COPY index.html /data/web/www/
👉 作用:
- 从宿主机 → 镜像
📌 特点:
- 不支持下载
- 不会自动解压
- 必须在 build 上下文内
4️⃣ ADD(增强版 COPY)
ADD nginx-1.22.1.tar.gz /usr/local/
ADD https://xxx.tar.gz /usr/local/
👉 多出来的能力:
- ✅ 自动下载 URL
- ✅ 自动解压 tar
📌 面试点:
能用 COPY 就别用 ADD(更可控)
5️⃣ WORKDIR(重点)
WORKDIR /usr/local
👉 作用:
- 设置工作目录(后续指令都在这里执行)
📌 特点:
- 默认
/ - 支持相对路径叠加
6️⃣ ENV(重点)
ENV WEB_ROOT=/data/web/www/
👉 作用:
- 设置环境变量
使用:
COPY index.html ${WEB_ROOT}
7️⃣ RUN(核心重点🔥)
RUN apt-get update && apt install -y nginx
👉 作用:
- 构建时执行命令
两种形式:
shell 形式(常用)
RUN apt-get update
exec 形式
RUN ["apt-get", "update"]
📌 面试重点:
- shell 会走
/bin/sh -c - exec 不支持变量替换
8️⃣ CMD(重点)
👉 作用:
- 容器启动默认执行命令
CMD ["nginx", "-g", "daemon off;"]
📌 特点:
- 只能有一个(后面会覆盖前面)
9️⃣ ENTRYPOINT(重点)
👉 作用:
- 程序入口(更强的 CMD)
📌 区别:
| CMD | ENTRYPOINT |
|---|---|
| 可被覆盖 | 不容易被覆盖 |
四、完整构建流程(Nginx 案例总结🔥)
🧩 整体步骤
1️⃣ 基础镜像
FROM ubuntu:22.04
2️⃣ 安装依赖(优化顺序重点)
RUN apt-get update -y && apt install -y \
build-essential \
libpcre3 libpcre3-dev \
zlib1g-dev
👉 ⚠️ 为什么放前面?
- 利用缓存(构建更快)
3️⃣ 设置变量
ENV NGINX_VERSION=nginx-1.22.1
ENV WEB_ROOT=/data/web/www/
4️⃣ 拷贝网页
COPY index.html ${WEB_ROOT}
5️⃣ 设置工作目录
WORKDIR /usr/local
6️⃣ 下载源码
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
7️⃣ 解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
8️⃣ 编译安装
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
9️⃣ 拷贝配置文件
COPY nginx.conf ./nginx/conf
五、Dockerfile 优化(面试高频🔥)
1️⃣ 减少层数
RUN apt-get update && apt install -y xxx
✔ 合并命令
2️⃣ 利用缓存(非常重要)
👉 不常变的放前面:
RUN apt install ...
COPY .
3️⃣ 使用 .dockerignore
避免把无关文件打包进去
4️⃣ 多阶段构建(高级)
👉 编译和运行分离(减小镜像)
六、Dockerfile vs commit(总结)
| 对比 | Dockerfile | commit |
|---|---|---|
| 自动化 | ✅ | ❌ |
| 可维护 | ✅ | ❌ |
| 可复现 | ✅ | ❌ |
| 体积优化 | ✅ | ❌ |
| 使用场景 | 正式环境 | 临时调试 |
一、CMD vs ENTRYPOINT(最容易混)
一句话区分
| 指令 | 作用 | 是否可被 docker run 覆盖 |
|---|---|---|
| CMD | 默认启动命令 | ✅ 会被覆盖 |
| ENTRYPOINT | 容器入口(主程序) | ❌ 默认不覆盖 |
CMD 三种写法(重点)
CMD ["executable","param1","param2"] ✅ 推荐(exec 形式)
CMD ["param1","param2"] ✅ 给 ENTRYPOINT 传参
CMD command param1 param2 ❌ shell 形式(不推荐)
✅ 最佳实践
-
想让容器"一直运行":
ENTRYPOINT ["nginx","-g","daemon off;"] -
想提供默认参数:
CMD ["--help"]
二、RUN vs CMD vs ENTRYPOINT 执行时机
| 指令 | 执行阶段 | 作用 |
|---|---|---|
| RUN | 构建镜像时 | 安装软件、编译 |
| CMD | 容器启动时 | 默认执行命令 |
| ENTRYPOINT | 容器启动时 | 固定入口程序 |
📌 口诀
RUN 建镜像,CMD/ENTRYPOINT 跑容器
三、EXPOSE:只是"声明",不是"映射"
核心点
EXPOSE 80/tcp
✅ 作用:
-
文档说明
-
docker ps能看到端口 -
配合
-P自动映射
❌ 不会:
-
自动开放端口
-
替代
-p
✅ 正确用法:
docker run -p 80:80 web1
📌 面试常问
EXPOSE 是给谁看的?
✅ 给运维 / 使用者看的文档
四、ARG vs ENV(构建 vs 运行)
对比总结
| 项目 | ARG | ENV |
|---|---|---|
| 作用阶段 | 构建阶段 | 构建 + 运行 |
| 是否可覆盖 | ✅ docker build --build-arg | ❌ 不可覆盖 |
| 是否进容器 | ❌ 默认不进 | ✅ 进容器 |
✅ 推荐写法(防空值):
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
✅ ENV 覆盖 ARG:
ARG VERSION
ENV VERSION=${VERSION:-v1.0}
五、VOLUME:防止数据丢失的"保底机制"
核心理解
VOLUME ["/data"]
✅ 特点:
-
自动创建匿名卷
-
容器删除 → 数据还在
-
用户没
-v也能用
✅ 典型场景:
-
MySQL
-
Nginx 日志
-
配置文件目录
❌ 不能:
-
指定宿主机目录
-
替代
-v
📌 一句话
VOLUME 是"防呆设计",不是"挂载命令"
六、SHELL:切换 shell 解释器
SHELL ["/bin/bash","-cvx"]
✅ 影响:
-
后续
RUN命令 -
调试非常有用
✅ 常见用途:
-
看命令展开
-
调试 shell 脚本
七、USER:安全最佳实践
USER nginx
✅ 推荐:
-
不要一直用 root
-
服务类程序用专用用户
✅ 你例子中的结论是对的:
nginx
mysql
八、HEALTHCHECK:容器"体检"
返回值含义
| 返回码 | 含义 |
|---|---|
| 0 | healthy |
| 1 | unhealthy |
| 2 | 保留 |
✅ 常见写法:
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
📌 docker ps会显示:
-
(healthy) -
(unhealthy)
九、ONBUILD:镜像的"钩子"
ONBUILD RUN echo "in build" >> /tmp/build.txt
✅ 触发条件:
- 别人
FROM你的镜像
✅ 使用场景:
-
基础镜像
-
SDK / 构建模板
❌ 不适合:
- 普通业务镜像
十、STOPSIGNAL:控制容器如何退出
STOPSIGNAL 9
| 信号 | 行为 |
|---|---|
| SIGTERM (15) | 优雅退出 |
| SIGKILL (9) | 强制杀死 |
✅ 你实验结论非常关键:
-
有 STOPSIGNAL 9 → 直接消失
-
默认 → nginx 优雅退出
十一、整体记忆表(面试版)
| 指令 | 阶段 | 是否可覆盖 | 典型用途 |
|---|---|---|---|
| FROM | 构建 | ❌ | 基础镜像 |
| RUN | 构建 | ❌ | 安装/编译 |
| CMD | 启动 | ✅ | 默认命令 |
| ENTRYPOINT | 启动 | ❌ | 主程序 |
| ARG | 构建 | ✅ | 构建参数 |
| ENV | 构建+运行 | ❌ | 环境变量 |
| EXPOSE | 文档 | ❌ | 端口声明 |
| VOLUME | 运行 | ❌ | 数据持久 |
| USER | 运行 | ❌ | 安全 |
| HEALTHCHECK | 运行 | ❌ | 健康检查 |
| ONBUILD | 构建 | ❌ | 基础镜像钩子 |
一、VOLUME到底做了什么?
Dockerfile 中的这一行:
VOLUME ["/data"]
✅ 实际效果只有一句话:
告诉 Docker:这个容器里的
/data目录,在运行时应该挂载一个 volume
⚠️ 注意:
-
构建阶段:
-
不会创建 volume
-
只是写进镜像的 metadata
-
-
运行阶段 (
docker run):-
Docker 发现这个声明
-
自动创建一个匿名 volume
-
挂载到容器的
/data
-
二、谁才是"创建数据卷"的主体?
| 行为 | 是谁做的 |
|---|---|
| 创建 volume | ✅ Docker(运行时) |
| 声明挂载点 | ✅ Dockerfile 中的 VOLUME |
| 管理 volume | ✅ Docker(docker volume命令) |
| 指定宿主机目录 | ❌ VOLUME做不到 |
✅ 真正创建卷的是:
docker run ...
或:
docker volume create
三、VOLUME≠ 数据卷管理
❌ VOLUME不能做的事情
-
不能指定宿主机目录
-
不能指定 volume 名字
-
不能删除 volume
-
不能设置 volume driver
✅ VOLUME能做的事情
-
防止用户忘记
-v导致数据丢失 -
保证容器删除后数据还在
-
明确"这个目录是数据目录"
📌 一句话总结
VOLUME是"声明",不是"管理"
四、匿名卷 vs 具名卷 vs bind mount
你这个例子非常典型,我们对比一下:
1️⃣ Dockerfile 中 VOLUME
VOLUME ["/data"]
运行时:
docker run myimage
Docker 会:
/var/lib/docker/volumes/<随机ID>/_data → /data
✅ 匿名卷
-
名字随机
-
容器删了,卷还在
-
需要
docker volume prune清理
2️⃣ 具名卷(推荐生产)
docker run -v mydata:/data myimage
✅ 可控、可复用、可管理
3️⃣ bind mount(开发常用)
docker run -v /host/data:/data myimage
✅ 直接绑定宿主机目录
五、为什么 MySQL / Nginx 镜像要用 VOLUME?
以 MySQL 为例:
VOLUME /var/lib/mysql
原因只有一个:
防止用户忘记
-v,删容器把数据库全删了
📌 这是 Docker 官方镜像的"安全兜底设计"
六、你实验中的结论是完全正确的 ✅
你做的这个流程:
-
VOLUME ["/data"] -
容器启动
-
Docker 自动创建匿名卷
-
容器删除
-
volume 仍然存在
✅ 完全符合 Docker 的设计行为
一、EXPOSE 到底"给谁用"?
✅ 正确理解
EXPOSE是:
写给"人"看的说明信息
-
写给镜像使用者
-
写给运维
-
写给
docker ps/docker inspect
📌 它不会:
-
打开端口
-
允许访问
-
做网络映射
-
影响容器间通信
二、容器之间通信,根本不需要 EXPOSE ✅
这是你最关心的点
容器之间能不能互通,和 EXPOSE 完全无关
例子
docker network create app-net
docker run -d --name db --network app-net mysql
docker run -d --name web --network app-net nginx
在 web容器里:
ping db
curl db:3306
✅ 只要:
-
在同一 network
-
容器内服务真的在监听端口
❌ 不需要:
-
EXPOSE
-
-p
📌 容器间通信 = Docker 网络 + 端口监听
三、那 EXPOSE 有什么实际作用?
✅ 1️⃣ 给 docker ps看
docker ps
你会看到:
PORTS
80/tcp
这个信息来自:
EXPOSE 80
✅ 2️⃣ 给 -P用(自动映射)
docker run -P nginx
Docker 会:
-
读取 EXPOSE 的端口
-
自动映射到宿主机随机端口
📌 没有 EXPOSE:
docker run -P
👉 什么都映射不出来
✅ 3️⃣ 给镜像使用者"提示"
EXPOSE 80 443
等于在说:
"这个镜像默认会监听 80 和 443"
四、EXPOSE 在内网 / 外网中的真实角色
| 场景 | 是否需要 EXPOSE |
|---|---|
| 容器 ↔ 容器 | ❌ 不需要 |
| 容器 ↔ 宿主机 | ❌ 不需要 |
docker run -p |
❌ 不需要 |
docker run -P |
✅ 需要 |
| 文档说明 | ✅ 需要 |
五、一个非常重要的对比(必记)
| 项目 | EXPOSE | -p |
|---|---|---|
| 是否真正开放端口 | ❌ | ✅ |
| 是否影响容器通信 | ❌ | ❌ |
| 是否影响宿主机访问 | ❌ | ✅ |
| 是否只是声明 | ✅ | ❌ |
六、你为什么会觉得"EXPOSE 是给内网用的"?
这是一个非常常见的误解来源:
Docker 官方文档 + 很多教程
会说:
"EXPOSE 用于声明容器监听的端口"
然后大家就误以为:
"哦,这是给内网容器访问用的"
❌ 实际是:
"声明" ≠ "生效"
七、生产环境正确姿势 ✅
✅ 推荐写法
EXPOSE 80
✅ 推荐运行方式
docker run -p 80:80 myimage
❌ 不推荐依赖
docker run -P
八、一句话终极记忆(强烈推荐)
**EXPOSE 是"说明书",不是"开关"**
容器通不通,看网络;宿主机能不能访问,看
-p
一、HEALTHCHECK 是干什么的?
检测容器内部服务是否"健康"
注意:
-
✅ 容器 进程还在 ≠ 服务正常
-
✅ 比如:
-
nginx 进程在
-
但 80 端口不响应
-
或返回 500
-
HEALTHCHECK就是干这个的。
二、Dockerfile 中的基本写法
✅ 标准格式
HEALTHCHECK [OPTIONS] CMD command
或关闭继承的健康检查:
HEALTHCHECK NONE
三、OPTIONS 参数(非常重要)
| 参数 | 默认值 | 含义 |
|---|---|---|
--interval=DURATION |
30s | 多久检查一次 |
--timeout=DURATION |
30s | 单次检查超时 |
--start-period=DURATION |
0s | 启动宽限期 |
--retries=N |
3 | 连续失败几次算 unhealthy |
✅ 示例:
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit 1
四、CMD 的返回值(核心)
Docker 只看 exit code:
| 返回值 | 含义 |
|---|---|
0 |
✅ healthy |
1 |
❌ unhealthy |
2 |
⚠️ 保留(不要用) |
📌 非 0 都是不健康
五、最常见的几种写法(实战)
✅ 1️⃣ HTTP 服务(最常用)
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
或:
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
✅ 2️⃣ TCP 端口检测
HEALTHCHECK CMD nc -z localhost 80 || exit 1
✅ 3️⃣ 只检查进程是否存在(不推荐)
HEALTHCHECK CMD pgrep nginx || exit 1
⚠️ 进程存在 ≠ 服务正常
✅ 4️⃣ 禁用健康检查
HEALTHCHECK NONE
六、Dockerfile 中完整示例(nginx)
FROM nginx:1.22
RUN apt-get update && apt-get install -y curl
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
CMD ["nginx", "-g", "daemon off;"]
七、构建 & 运行后怎么看?
✅ 1️⃣ docker ps
docker ps
你会看到:
STATUS
Up 10 seconds (healthy)
或:
Up 10 seconds (unhealthy)
✅ 2️⃣ docker inspect(最详细)
docker inspect <container_id>
重点字段:
"State": {
"Health": {
"Status": "healthy",
"FailingStreak": 0,
"Log": [...]
}
}
✅ 3️⃣ 查看失败原因
docker inspect --format='{{json .State.Health}}' <container>
八、HEALTHCHECK 在 docker run 中也能用
docker run \
--health-cmd="curl -f http://localhost/ || exit 1" \
--health-interval=5s \
--health-timeout=3s \
--health-retries=3 \
nginx
📌 docker run 的优先级 > Dockerfile
九、在 Docker Compose 中怎么用?
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 5s
timeout: 3s
retries: 3
Docker 镜像制作命令:docker build
功能
- 使用 Dockerfile 创建镜像。
语法
docker build [OPTIONS] PATH | URL | -
PATH:Dockerfile 所在的目录路径URL:远程 Git 仓库地址-:从标准输入读取 Dockerfile
关键参数
| 参数 | 说明 |
|---|---|
--build-arg=[] |
设置镜像创建时的变量 |
-f |
指定 Dockerfile 路径 |
--label=[] |
设置镜像元数据(标签信息) |
--no-cache |
构建镜像时不使用缓存 |
--pull |
尝试更新基础镜像到新版本 |
--quiet, -q |
安静模式,只输出镜像 ID |
--tag, -t |
指定镜像名称和标签,例如 name:tag,可为一个镜像设置多个标签 |
--network |
构建期间设置 RUN 指令的网络模式,默认 default |
示例
docker build -t mynginx:v1 .
- 在当前目录下使用 Dockerfile 构建镜像
- 镜像命名为
mynginx,标签为v1
Dockerfile 编写优秀实践
1. 善用 .dockerignore 文件
- 在执行
docker build时,忽略不必要的路径和文件 - 优点:
- 减少发送到 Docker 守护进程的数据量
- 加快镜像构建速度
2. 镜像的多阶段构建(Multi-stage Build)
- 将 编译 和 运行 分开
- 仅保留最终镜像所需环境
- 优势:
- 镜像体积更小
- 保持清晰的构建流程
- 注意:单独维护多个 Dockerfile 也可实现类似效果,但更复杂
3. 合理使用缓存
- 利用 Docker 的 缓存机制,提高构建速度
- 建议:
- 内容不变的指令尽量放在前面
- 减少 COPY 目录下的文件数量
- 将不经常改变的依赖先处理
4. 基础镜像选择
- 优先使用官方镜像
- 尽量选 小体积镜像 :
- 应用镜像:
node:slim - 系统镜像:
alpine、busybox、debian
- 应用镜像:
- 避免使用过大的基础镜像(如完整 Ubuntu),减少最终镜像臃肿
5. 减少镜像层数
- 尽量合并
RUN、ADD、COPY指令 - 示例:
RUN apt-get update && apt-get install -y \
package1 package2 package3 \
&& rm -rf /var/lib/apt/lists/*
- 优点:
- 镜像层少
- 镜像体积更小
6. 精简镜像用途
- 每个镜像尽量 功能单一
- 避免构造大而复杂、多用途的镜像
- 有利于维护和升级
7. 减少外部源干扰
- 如果必须从外部下载数据:
- 指定稳定 URL
- 带版本信息
- 保证镜像可复现,其他人使用不出错
8. 减少不必要的包安装
- 只安装运行应用所需的依赖包
- 避免安装多余工具
- 直接降低镜像体积,提高安全性
Docker 镜像制作常见问题整理
1. ADD 与 COPY 的区别
| 指令 | 功能 | 使用场景 |
|---|---|---|
| ADD | 可将本地文件/目录或远程 URL 对应的资源复制到镜像 | 需要下载远程资源或解压归档文件 |
| COPY | 仅能将本地文件/目录复制到镜像 | 仅需要拷贝本地文件或压缩包到镜像 |
建议:如果只是简单拷贝本地文件,使用
COPY即可。
2. CMD 与 ENTRYPOINT 的区别
| 指令 | 功能 | 特点 |
|---|---|---|
| ENTRYPOINT | 指定容器启动后执行的命令 | 容器行为像一个可执行程序,docker run 参数会传给 ENTRYPOINT;Dockerfile 中只能有一个 ENTRYPOINT |
| CMD | 指定默认的运行命令或参数 | 可被 docker run 后的命令覆盖 |
组合使用
ENTRYPOINT:指定默认命令CMD:指定默认参数- 实现可灵活覆盖参数又保持默认命令的效果
3. 多个 FROM 指令的使用(多阶段构建)
- 每条
FROM指令都是一个构建阶段 - 最终镜像以最后一条
FROM为准 - 前置阶段可提供文件给后续阶段,常用于:
- 编译环境与运行环境分离
- 只保留运行环境的最小镜像,提高效率和安全性
4. 快照与 Dockerfile 制作镜像的区别
- 快照:手动操作生成的镜像,难以复现和管理
- Dockerfile:声明式定义镜像,可版本控制、自动化、可复现
原因:使用 Dockerfile 可以更好地管理和重建镜像
5. 空悬镜像(dangling image)
- 特征:仓库名、标签均为
<none> - 原因:
- 镜像更新后,旧镜像名被覆盖
- Dockerfile 构建时重复生成同名镜像
- 查看空悬镜像:
docker image ls -f dangling=true
- 可安全删除,因为一般已经失去存在价值
6. 中间层镜像
- 用途:加速镜像构建、重复利用资源
- 特征:
- Docker 构建每条指令产生一层 image
- 中间层镜像可能无标签,但被上层镜像依赖
- 查看中间层镜像:
docker image ls -a
- 不建议删除,否则上层镜像可能出错
- 不会占用额外存储,相同层只存一份
Docker 镜像原理笔记
一、操作系统基础
- 操作系统由以下子系统组成:
- 进程调度子系统
- 进程通信子系统
- 内存管理子系统
- 设备管理子系统
- 文件管理子系统
- 网络通信子系统
- 作业控制子系统
- Linux 文件管理子系统由 bootfs 和 rootfs 组成:
- bootfs
- 包含 bootloader 和 kernel
- 功能:引导加载内核,启动时加载,加载完成后被卸载
- Docker 镜像底层对应 bootfs
- rootfs
- 包含 Linux 标准目录
/dev,/proc,/bin,/etc等 - 对应不同 Linux 发行版(Ubuntu、CentOS 等)
- 包含 Linux 标准目录
- bootfs
二、Union FS(联合文件系统)
- 将多个目录合并挂载到一个虚拟目录下
- 特性:
- 支持只读和可读写目录合并
- 写时复制(Copy-on-Write,CoW)
- 写时复制原理
- 资源初次修改才创建新副本
- 未修改资源共享,节省存储和内存
- 修改操作只在新副本上发生
三、Docker 镜像概念
- 镜像 = 多层 Union FS 文件系统
- 每一层称为 layer
- 镜像特点:
- 共享宿主机内核
- Base 镜像是最小 Linux 发行版
- 同一 Docker 主机支持不同 Linux 发行版
- 分层结构,可上层引用下层,实现资源最大化共享
- 容器层可写,采用 CoW 技术,仅保存变化部分
- 容器层以下都是只读
- Docker 从上到下查找文件
四、Docker 分层存储实现原理
1. 支持的 Union FS 类型
- AUFS、overlay、overlay2、DeviceMapper、VSF
- 各发行版使用:
- CentOS: overlay2 / overlay
- Debian: aufs
- RedHat: devicemapper
- 推荐:overlay2(稳定,Docker 默认)
2. Union FS 原理
- 镜像 = 多个只读层叠加
- 容器启动时:
- 加载镜像只读层
- 在顶部加 读写层
- 文件修改:
- Copy-up 到读写层
- 原只读层保持不变
- 文件删除:
- 在读写层创建 whiteout 文件
- 屏蔽下层文件,但下层文件不删除
3. overlay2 层级结构
| 层 | 作用 |
|---|---|
| lowerdir | 镜像只读层,包括 bootfs/rootfs 及 Dockerfile 构建的镜像层 |
| upperdir | 容器读写层,使用 CoW,存储文件修改 |
| workdir | 临时中间层,修改操作先进入 workdir,再移动到 upperdir |
| merged | 用户看到的统一目录,展示 upperdir 或 lowerdir 的文件内容 |
4. 文件操作机制
- 读取 :
- 文件在 upperdir → 直接读取
- 文件不在 upperdir → 从 lowerdir 读取
- 写入 :
- 首次写入 → copy-up 到 upperdir
- 再次写入 → 修改 upperdir 副本
- 删除 :
- 创建 whiteout 文件隐藏下层文件
- lowerdir 不受影响
注意:容器层文件删除只是"遮挡",image 层不会改变
五、Docker 镜像加载原理
- 启动流程:
- bootfs 加载 → 内核加载完成后卸载
- rootfs 加载 → 只读检查
- 利用 union mount 将可写层挂载到 readonly rootfs 上
- 运行时:
- 所有只读镜像层共享
- 容器最顶层可读写
- 利用命名空间、控制组、rootfs 实现容器隔离与组装
六、核心概念总结
- 镜像是多层只读文件系统叠加
- 容器层为可写层,采用 CoW 技术
- 文件删除通过 whiteout 实现,原镜像不变
- overlay2 是官方推荐,稳定高效
- 镜像分层结构实现资源共享和存储节省
实战一:镜像分层存储实战
一、先用一句人话理解整个实战
👉 Docker 镜像本质就是:
一层一层"文件夹"叠起来,最后拼成一个完整的 Linux 系统
而 overlay2 做的事就是:
把多个目录"叠加"成一个目录给你看
二、先建立一个"脑子里的模型"(最重要)
想象:
第3层(你修改的) ← 可写
第2层(nginx配置)
第1层(系统文件)
第0层(Debian基础)
Docker 实际干的事:
👉 把这些层 叠起来
你看到的是:
一个完整的 Linux 系统(假的,但看起来真的)
三、磁盘上到底长什么样?(重点)
路径:
/var/lib/docker/overlay2/
结构👇
overlay2/
├── abc123/
│ ├── diff ← 这一层的真实文件
│ ├── lower ← 它的"爸爸层"
│ ├── link ← 短名字
│ └── work
├── def456/
├── ghi789/
├── l/ ← 所有层的"快捷方式"
四、逐个解释(这是核心)
1️⃣ diff ------ 真正存文件的地方
👉 你可以理解为:
每一层就是一个"文件夹快照"
比如:
.../diff/usr/sbin/nginx
说明:
👉 nginx 就存在这一层里
📌 重点:
- 每一层只存"自己新增或修改的文件"
- 不重复存
2️⃣ lower ------ 层之间的"族谱"
cat lower
输出:
l/xxx:l/yyy
👉 意思是:
我这一层,是基于 xxx 和 yyy 这两层的
📌 本质:
👉 层与层之间是链式关系
3️⃣ link ------ 层的短名字
cat link
得到:
QJTI7BHG2F...
然后你去:
overlay2/l/QJTI7BHG2F...
会发现:
👉 它是一个软链接 → 指向 diff
📌 作用:
👉 缩短路径(不然太长)
4️⃣ l 目录 ------ 所有层的"快捷方式集合"
overlay2/l/
里面全是:
短ID → 指向某个 diff
👉 类似 Windows 快捷方式
五、重点来了:容器启动前 vs 启动后
❌ 没启动容器时
只有:
diff(镜像层)
lower(关系)
👉 没有:
- upper
- merged
✅ 启动容器后
docker run -d nginx
系统帮你创建:
upperdir ← 你可以写
merged ← 给你看的"完整系统"
workdir ← 临时用
六、最关键:merged 到底是什么?
👉 merged 就是:
把所有层 + 你的修改 拼起来的"最终视图"
你进去看:
cd merged
ls
看到:
/bin
/etc
/usr
📌 这就是:
👉 你以为的 Linux 系统
七、读文件过程(超级重要)
当你访问:
/usr/sbin/nginx
Docker 怎么找?
👉 从上往下找:
-
upper(你改过?)
-
layer3
-
layer2
-
layer1
👉 找到就停
八、写文件过程(COW核心)
假设你修改 nginx:
第一次修改:
-
从下层复制到 upper(copy_up)
-
修改 upper 中的副本
👉 原来的不动!
第二次修改:
直接改 upper
📌 结论:
👉 所有修改都在 upper 层
九、删除文件(非常容易考)
你删除:
rm /usr/sbin/nginx
实际发生:
❌ 不会删底层文件
✅ 创建一个:
whiteout 文件(遮罩)
👉 效果:
你看不到 nginx
但其实还在
十、为什么 docker commit 会越来越大?
因为:
删除 ≠ 真删除
只是遮住
👉 所以:
- 层越来越多
- 数据越来越大
十一、把整个流程串起来(最重要总结)
镜像构建:
Dockerfile 每一行 → 一个 layer(diff)
镜像存储:
diff 存内容
lower 存关系
l 存快捷方式
容器运行:
镜像层(只读)
- upper(可写)
= merged(你看到的系统)
十二、最简单理解版本(一定要记)
👉 Docker 做的事其实就一句话:
用 overlay2 把多个文件夹叠起来,再给你一个"假的完整系统"