KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

00 · 镜像 — keel 龙骨

类型:主题概念 · 应用课对应:01 章 · 镜像与容器

类型:主题概念 · 应用课对应:01 章 · 镜像与容器

这一页只回答四件事:它为什么会出现、它是什么、它长什么样、它怎么工作。不讲容器怎么跑,也不讲内核。

一、它为什么会出现

要把一个在本机跑得好好的服务搬到另一台机器上,只搬源码是不够的:目标机器上可能没有对的运行时版本、没有那个系统库、环境变量不一样、时区不一样。于是出现了一句所有人都说过的话——在我机器上是好的。

它面对的需求是:把"运行环境"从"机器"里剥离出来,变成一个可以整份传递、可以逐字节核对的物件。

它解决的问题就是那句抱怨的另一半——不是"让程序不依赖环境",而是让环境本身可以被复制。

二、它是什么

一句话:镜像是只读的文件系统,加上一份启动配置。

它由什么组成 里面是什么 谁读它
层堆叠(若干层) 系统包、运行时、你的代码;每一层只记"相对上一层改了什么" 运行时解压、按顺序叠加
一份 config 启动命令、对外端口、环境变量默认值、用哪个用户跑 运行时据此决定怎么起进程

它不是另外三样东西——区分清楚能省掉一半的困惑:

三、它长什么样

它由一份 Dockerfile 写出来,产出的东西就是"层 + config":

FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "dist/index.js"]

这份文件读成的东西:

镜像 = [ 层 0 ] FROM node:22-alpine
       [ 层 1 ] COPY 依赖清单 + RUN npm ci
       [ 层 2 ] COPY 源码与产物
       + config(WORKDIR /app、EXPOSE 3000、USER node、CMD ["node","dist/index.js"] …)

最后一行里有个容易忽略的差别,它解释了为什么有些指令"看起来什么都没干":

Dockerfile 里的行 落到哪里
RUN / COPY / ADD(动了文件系统) 变成一个层
CMD / ENV / EXPOSE / USER 进 config,不产生层

四、它怎么工作

从一份 Dockerfile 到在一台机器上跑起来,是四步:

① 构建   构建器逐条读 Dockerfile:每改一次文件系统就产出一层,配置写进 config
   ↓
② 存放   层按内容摘要存在本地库里;内容相同就是同一个对象,不存第二份
   ↓
③ 传递   push / pull 时,本地已有的层不用重传
   ↓
④ 运行   docker run:层被解压叠加成只读文件系统 → 上面加一个可写层 → 起进程

四步里只有第 ④ 步在"跑",前两步都是"造与存"——应用课里所有构建类问题(慢、体积大、密钥进镜像)都发生在 ①②。

五、它会带出哪些概念

理解镜像时有两个概念绕不过去,各自有一页:

六、回到应用课

应用课里的那句话 现在你知道它为什么成立
"镜像是不可变的" 它的两块组成(层、config)都是只读的;能写的地方只有运行时新加的那一个可写层
"同一个产物必须能进所有环境" 镜像里装的是代码与运行时;环境差异(数据库地址、密钥)必须留在镜像外面
"按 digest 固定基础镜像,不要用 tag" 固定的对象是这份"层 + config"清单的摘要;tag 只是一个可以被改指向的名字

这一页改变了你的哪个判断:看到一份 Dockerfile,你不再只问"它能不能跑",而是先问**"哪几行会变成一个层、哪几行只进 config"**——前者决定构建时间与镜像体积,后者决定容器启动时的行为。

自测题

  1. RUN npm ci 与 CMD ["node","dist/index.js"] 都在 Dockerfile 里,哪一个会变成一个层?另一个为什么不用?
  2. 为什么"镜像不可变"和"两张镜像能共用一层"其实是同一件事的两面?

进入 keel 阅读