Docker学习路径——4、制作/更改镜像

Docker 镜像构建原理与实战:从分层存储到自定义镜像制作

在 Docker 的世界中,镜像 (Image) 是一切的起点。理解镜像的分层结构、加载原理,并掌握自定义镜像的构建方法,是迈向高效容器化开发的关键一步。本文将深入剖析 Docker 镜像的底层机制,并通过两种主流方式(docker commitDockerfile)演示如何创建自己的镜像。


一、Docker 镜像的核心:分层存储架构

1. 镜像为什么是分层的?

Docker 镜像采用 UnionFS (联合文件系统) 技术,将文件系统拆分为多个只读层(Read-only Layers),每一层代表一次变更(如安装软件、添加文件)。这种设计带来三大核心优势:

  • 资源共享:多个镜像可共享相同的基础层(如 Ubuntu 20.04),节省磁盘空间
  • 高效传输:拉取镜像时仅下载缺失的层,加速分发
  • 版本控制:每层都有唯一 ID,支持回滚与审计

📌 关键概念

  • 基础镜像 (Base Image):最底层,通常为精简 OS(如 scratch, alpine
  • 中间层 (Intermediate Layers):由 RUN, COPY 等指令生成
  • 可写层(Container Layer):容器运行时在顶层创建的读写层

2. 镜像加载原理:bootfs 与 rootfs

当 Docker 启动容器时,会按顺序加载两层文件系统:

层级 组件 作用 容器运行时状态
bootfs bootloader + kernel 引导系统启动 卸载(由宿主机内核接管)
rootfs /bin, /etc, /usr... 标准 Linux 目录结构 只读挂载(作为镜像层)

🔍 为什么 bootfs 会被卸载

Docker 容器直接复用宿主机内核,无需 Guest OS。因此启动后立即卸载 bootfs,仅保留 rootfs 作为应用运行环境。


3. 容器的读写层:Copy-on-Write (CoW)

  • 镜像层(Image Layers):全部只读,不可修改
  • 容器层(Container Layer):位于最顶层,所有写操作(创建/修改/删除文件)都在此进行
  • CoW 机制
    • 读文件 → 从上至下搜索各层,返回首个匹配
    • 修改文件 → 将原文件复制到容器层再修改(避免污染镜像层)
    • 删除文件 → 在容器层标记"whiteout"(隐藏下层同名文件)

💡 重要结论
容器停止后,容器层数据会丢失 !持久化数据必须通过 VolumeBind Mount 实现。


二、方法一:通过 docker commit 制作镜像(不推荐用于生产)

docker commit 允许你基于正在运行的容器创建新镜像。虽然简单直观,但存在严重缺陷:

  • ❌ 无法追溯构建步骤(无历史记录)
  • ❌ 镜像臃肿(包含临时文件、缓存)
  • ❌ 不可重复(依赖容器当时的状态)

实战案例:为 Ubuntu 镜像添加 Vim

步骤 1:启动并进入容器
bash 复制代码
# 运行 Ubuntu 容器(交互式)
$ docker run -it --name ubuntu-dev ubuntu:20.04 /bin/bash

# 在容器内更新包索引并安装 Vim
root@container-id:/# apt-get update
root@container-id:/# apt-get install -y vim
root@container-id:/# exit  # 退出但不删除容器
步骤 2:提交为新镜像
bash 复制代码
# 语法:docker commit [OPTIONS] CONTAINER REPOSITORY[:TAG]
$ docker commit \
  -a "Your Name" \          # 作者信息
  -m "Install vim editor" \ # 提交说明
  ubuntu-dev \              # 源容器名称或ID
  my-ubuntu:vim-1.0         # 新镜像名:标签

# 输出新镜像ID
sha256:8a3b5c7d9e1f2a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4
步骤 3:验证新镜像
bash 复制代码
# 查看镜像列表
$ docker images | grep my-ubuntu
my-ubuntu   vim-1.0   8a3b5c7d9e1f   2 minutes ago   120MB

# 测试 Vim 是否可用
$ docker run --rm -it my-ubuntu:vim-1.0 vim --version
VIM - Vi IMproved 8.1 (2018 May 18)

⚠️ 为什么生产环境不推荐 commit

  • 镜像包含 apt-get 缓存(浪费 50MB+ 空间)
  • 无法自动化重建(下次需手动重复操作)
  • 违反 不可变基础设施 原则

三、方法二:通过 Dockerfile 构建镜像(生产标准)

Dockerfile 是一个文本指令集 ,明确定义了镜像的构建步骤。它解决了 commit 的所有痛点:

  • 可重复 :任何人执行 docker build 都能得到相同结果
  • 轻量:通过多阶段构建剔除构建依赖
  • 可审计:每条指令对应一个镜像层,清晰透明

实战案例:编写 Vim 镜像的 Dockerfile

步骤 1:创建项目目录
bash 复制代码
mkdir my-ubuntu-vim && cd my-ubuntu-vim
步骤 2:编写 Dockerfile
dockerfile 复制代码
# 使用官方 Ubuntu 20.04 作为基础镜像
FROM ubuntu:20.04

# 设置非交互式安装(避免 apt 弹窗)
ENV DEBIAN_FRONTEND=noninteractive

# 更新包索引并安装 Vim,最后清理缓存(关键!)
RUN apt-get update && \
    apt-get install -y vim && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# 设置默认命令(可选)
CMD ["/bin/bash"]
步骤 3:构建镜像
bash 复制代码
# 语法:docker build -t <镜像名:标签> <上下文路径>
$ docker build -t my-ubuntu:vim-df-1.0 .

# 构建过程输出(关键层)
Step 1/4 : FROM ubuntu:20.04
 ---> ba6acccedd29
Step 2/4 : ENV DEBIAN_FRONTEND=noninteractive
 ---> Running in abc123...
 ---> def456...  # 新层ID
Step 3/4 : RUN apt-get update && ...
 ---> ghi789...  # 包含 Vim 的层
Step 4/4 : CMD ["/bin/bash"]
 ---> jkl012...
Successfully built jkl012...
Successfully tagged my-ubuntu:vim-df-1.0
步骤 4:对比镜像大小
bash 复制代码
$ docker images | grep my-ubuntu
my-ubuntu   vim-1.0      8a3b5c7d9e1f   120MB  # commit 方式
my-ubuntu   vim-df-1.0   jkl012...      75MB   # Dockerfile 方式(清理缓存后)

Dockerfile 优势体现

通过 apt-get clean && rm -rf /var/lib/apt/lists/* 清理缓存,减少 45MB 体积


四、Dockerfile 最佳实践

1. 减少镜像层数

  • 合并 RUN 指令(用 && 连接命令)
  • 避免不必要的层(如单独 ENV 可合并到 RUN

2. 利用构建缓存

  • 变化频率低 的指令放在前面(如 FROM, COPY requirements.txt
  • Docker 会复用未变化层的缓存,加速构建

3. 多阶段构建(Multi-stage Build)

dockerfile 复制代码
# 第一阶段:编译应用
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .

# 第二阶段:运行时镜像
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]
  • 效果:最终镜像仅包含可执行文件,不含 Go 编译器(体积减少 90%+)

4. 安全加固

  • 使用非 root 用户运行:

    dockerfile 复制代码
    RUN adduser --disabled-password --gecos '' appuser
    USER appuser
  • 扫描漏洞:docker scan my-ubuntu:vim-df-1.0


五、总结:两种方法对比

特性 docker commit Dockerfile
可重复性 ❌ 依赖容器状态 ✅ 指令明确
镜像大小 ❌ 包含冗余数据 ✅ 可优化清理
可维护性 ❌ 无构建历史 ✅ 版本可控
适用场景 临时调试、紧急修复 所有生产环境

🚀 行动建议

  • 日常开发:用 commit 快速验证想法
  • 交付生产:必须使用 Dockerfile
  • 进阶学习:掌握多阶段构建、BuildKit 优化

掌握镜像构建原理与 Dockerfile 编写,你就拥有了打造标准化、轻量化、安全化 容器应用的能力。下一步,我们将探索如何用 docker-compose 编排多容器应用!

🔗 延伸阅读

相关推荐
小锋学长生活大爆炸5 小时前
【教程】在Docker中部署Hermes Agent
docker·容器·agent·教程·工具·openclaw·hermes
chools10 小时前
【AI超级智能体】快速搞懂工具调用Tool Calling 和 MCP协议
java·人工智能·学习·ai
自信1504130575911 小时前
重生之从0开始学习c++之模板初级
c++·学习
nashane11 小时前
HarmonyOS 6学习:解决异步场景下Toast提示框无法弹出的UI上下文丢失问题
学习·ui·harmonyos·harmony app
AI服务老曹12 小时前
异构计算时代的安防底座:基于 Docker 的 X86/ARM 双模部署与 NPU 资源池化实战
arm开发·docker·容器
码喽7号14 小时前
Vue学习七:MockJs前端数据模拟
前端·vue.js·学习
三品吉他手会点灯14 小时前
STM32F103 学习笔记-21-串口通信(第4节)—串口发送和接收代码讲解(中)
笔记·stm32·单片机·嵌入式硬件·学习
筱顾大牛15 小时前
使用docker部署到服务器
docker·部署
EnglishJun16 小时前
ARM嵌入式学习(二十三)--- I2C总线和SPI总线
arm开发·学习