Docker 核心技术和实现原理

Docker 镜像制作笔记

一、镜像制作的原因

当官方镜像无法满足特定需求时,需要自定义镜像,常见原因包括:

  1. 代码打包
    将自编写的代码打包到镜像中,随镜像发布。
  2. 安全性考虑
    第三方镜像可能存在安全漏洞,需要自制镜像。
  3. 特定功能需求
    官方镜像无法满足功能,如数据库审计、特殊配置。
  4. 公司内部规范
    基于公司内部操作系统或标准构建镜像。

二、镜像制作方式

1. 快照方式(偶尔制作的镜像)

  • 思路:在基础镜像上启动容器,安装所需软件和配置后,创建快照。
  • 命令 :docker commit
  • 语法:
复制代码

docker commit OPTIONS CONTAINER REPOSITORY\[:TAG]

  • 常用参数 :
    • -a:提交作者
    • -c:使用 Dockerfile 指令创建镜像,可修改启动指令
    • -m:提交说明
    • -p:提交时暂停容器

2. Dockerfile 构建方式(经常更新的镜像)

  • 思路 :将安装流程写入 Dockerfile,通过 docker build 构建镜像。
  • 特点 :
    • 可重复构建
    • 易于版本管理
    • 自动化 CI/CD 支持

三、快照方式实战

实战一:C++ HelloWorld 镜像

  1. 准备工作目录:
复制代码

mkdir -p /data/maxhou/commitimage

cd /data/maxhou/commitimage

  1. 创建 C++ 源码:
复制代码

#include <stdio.h>

int main() {

printf("hello docker!\n");

return 0;

}

  1. 启动 CentOS 容器:
复制代码

docker run -it --name mycppcommit centos:7 bash

  1. 替换国内软件源(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

  1. 安装编译器并创建源代码目录:
复制代码

yum install -y gcc

mkdir /src/

  1. 拷贝源码到容器:
复制代码

docker cp ./demo.c mycppcommit:/src

  1. 编译运行测试:
复制代码

cd /src

gcc demo.c -o demo

./demo # 输出 hello docker!

  1. 提交为镜像:
复制代码

docker commit mycppcommit mycppimg:v1.0

docker images

  1. 运行镜像验证:
复制代码

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 官方镜像的"安全兜底设计"


六、你实验中的结论是完全正确的 ✅

你做的这个流程:

  1. VOLUME ["/data"]

  2. 容器启动

  3. Docker 自动创建匿名卷

  4. 容器删除

  5. 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>
  • 原因:
    1. 镜像更新后,旧镜像名被覆盖
    2. Dockerfile 构建时重复生成同名镜像
  • 查看空悬镜像:
复制代码

docker image ls -f dangling=true

  • 可安全删除,因为一般已经失去存在价值

6. 中间层镜像

  • 用途:加速镜像构建、重复利用资源
  • 特征:
    • Docker 构建每条指令产生一层 image
    • 中间层镜像可能无标签,但被上层镜像依赖
  • 查看中间层镜像:
复制代码

docker image ls -a

  • 不建议删除,否则上层镜像可能出错
  • 不会占用额外存储,相同层只存一份

Docker 镜像原理笔记

一、操作系统基础

  • 操作系统由以下子系统组成:
    • 进程调度子系统
    • 进程通信子系统
    • 内存管理子系统
    • 设备管理子系统
    • 文件管理子系统
    • 网络通信子系统
    • 作业控制子系统
  • Linux 文件管理子系统由 bootfs 和 rootfs 组成:
    1. bootfs
      • 包含 bootloader 和 kernel
      • 功能:引导加载内核,启动时加载,加载完成后被卸载
      • Docker 镜像底层对应 bootfs
    2. rootfs
      • 包含 Linux 标准目录 /dev, /proc, /bin, /etc 等
      • 对应不同 Linux 发行版(Ubuntu、CentOS 等)

二、Union FS(联合文件系统)

  • 将多个目录合并挂载到一个虚拟目录下
  • 特性:
    • 支持只读和可读写目录合并
    • 写时复制(Copy-on-Write,CoW)
  • 写时复制原理
    • 资源初次修改才创建新副本
    • 未修改资源共享,节省存储和内存
    • 修改操作只在新副本上发生

三、Docker 镜像概念

  • 镜像 = 多层 Union FS 文件系统
  • 每一层称为 layer
  • 镜像特点:
    1. 共享宿主机内核
    2. Base 镜像是最小 Linux 发行版
    3. 同一 Docker 主机支持不同 Linux 发行版
    4. 分层结构,可上层引用下层,实现资源最大化共享
    5. 容器层可写,采用 CoW 技术,仅保存变化部分
    6. 容器层以下都是只读
    7. Docker 从上到下查找文件

四、Docker 分层存储实现原理

1. 支持的 Union FS 类型

  • AUFS、overlay、overlay2、DeviceMapper、VSF
  • 各发行版使用:
    • CentOS: overlay2 / overlay
    • Debian: aufs
    • RedHat: devicemapper
  • 推荐:overlay2(稳定,Docker 默认)

2. Union FS 原理

  • 镜像 = 多个只读层叠加
  • 容器启动时:
    1. 加载镜像只读层
    2. 在顶部加 读写层
    3. 文件修改:
      • Copy-up 到读写层
      • 原只读层保持不变
    4. 文件删除:
      • 在读写层创建 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 镜像加载原理

  • 启动流程:
    1. bootfs 加载 → 内核加载完成后卸载
    2. rootfs 加载 → 只读检查
    3. 利用 union mount 将可写层挂载到 readonly rootfs 上
  • 运行时:
    • 所有只读镜像层共享
    • 容器最顶层可读写
    • 利用命名空间、控制组、rootfs 实现容器隔离与组装

六、核心概念总结

  1. 镜像是多层只读文件系统叠加
  2. 容器层为可写层,采用 CoW 技术
  3. 文件删除通过 whiteout 实现,原镜像不变
  4. overlay2 是官方推荐,稳定高效
  5. 镜像分层结构实现资源共享和存储节省

实战一:镜像分层存储实战

一、先用一句人话理解整个实战

👉 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 这两层的

📌 本质:

👉 层与层之间是链式关系


复制代码

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 怎么找?

👉 从上往下找:

复制代码
  1. upper(你改过?)

  2. layer3

  3. layer2

  4. layer1

👉 找到就停


八、写文件过程(COW核心)

假设你修改 nginx:

第一次修改:

复制代码
  1. 从下层复制到 upper(copy_up)

  2. 修改 upper 中的副本

👉 原来的不动!


第二次修改:

复制代码

直接改 upper


📌 结论:

👉 所有修改都在 upper 层


九、删除文件(非常容易考)

你删除:

复制代码

rm /usr/sbin/nginx

实际发生:

❌ 不会删底层文件

✅ 创建一个:

复制代码

whiteout 文件(遮罩)

👉 效果:

复制代码

你看不到 nginx

但其实还在


十、为什么 docker commit 会越来越大?

因为:

复制代码

删除 ≠ 真删除

只是遮住

👉 所以:

  • 层越来越多
  • 数据越来越大

十一、把整个流程串起来(最重要总结)

镜像构建:

复制代码

Dockerfile 每一行 → 一个 layer(diff)


镜像存储:

复制代码

diff 存内容

lower 存关系

l 存快捷方式


容器运行:

复制代码

镜像层(只读)

  • upper(可写)

= merged(你看到的系统)


十二、最简单理解版本(一定要记)

👉 Docker 做的事其实就一句话:

用 overlay2 把多个文件夹叠起来,再给你一个"假的完整系统"

相关推荐
VIP_CQCRE40 分钟前
给团队搭一个 AI 工作台:Open WebUI 接入 Ace Data Cloud 实战
docker·ai·rag·open webui·ace data cloud
weifont1 小时前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化
拂拉氏1 小时前
【知识讲解】 Linux之动静态库的了解
linux·运维·服务器·动静态库
Mikko72 小时前
Linux 重启后日志没了?进程被 OOM 杀了去哪看?logrotate 之后日志还往旧文件写?三台真机实测日志与性能排查
linux·运维·centos
光依旧2 小时前
发版就掉请求?Spring Boot优雅停机的四步
运维·spring boot·nginx·高可用·优雅停机·mcp server
yichengerp3 小时前
电子行业常用erp系统有哪些?推荐适合中小电子厂的易呈erp软件,操作简单,易落地
大数据·运维·人工智能·云计算·制造
天国梦4 小时前
国密门禁品牌怎么选?2026年最新3个避坑要点
运维·服务器·数据库·python
程序员老赵4 小时前
Docker 部署 Portainer CE:轻松搭建容器管理平台
docker·容器·开源
TTATTC5 小时前
Gmail、Drive、日历共用一套命令:gws 如何在运行时把 Discovery 文档编译成 CLI
docker·开源
信可维5 小时前
自动化操作多平台账号:登录态、双会话与 User Data 的 3 个坑
运维·自动化