Docker 里最核心的 4 个对象你先把这 4 个概念分清:
镜像 image
容器 container
镜像层 layer
仓库 registry/repository
———
1. 镜像是什么
镜像可以理解成:
一个只读的运行模板
它里面包含:
操作系统基础环境
Python
你的依赖包
你的项目代码
容器启动命令
例如:
python:3.11-slim
它是一个基础镜像。 而你后面构建出来的:
resume-assistant:latest
是一个业务镜像。
镜像本身不会“运行”,它只是一个模板。
———
2. 容器是什么
容器可以理解成:
镜像运行起来之后的实例
比如:
docker run resume-assistant:latest
这时 Docker 会基于镜像创建一个容器。
所以关系是:
镜像 -> 启动 -> 容器
就像:
类 -> 实例
模板 -> 实际运行对象
一个镜像可以启动多个容器。
———
3. 镜像层是什么
这是你刚才问题的关键。
Docker 镜像不是一个“大文件硬拷贝”,而是由多层组成的。
例如你的项目镜像可能逻辑上长这样:
第1层:Linux 基础系统层 第2层:Python 3.11 层 第3层:pip 安装 requirements.txt 的依赖层 第4层:复制 app/ 代码层 第5层:启动命令和环境变量层
这些层会被缓存和复用。
为什么这很重要
假设你已经有:
python-base:3.11-slim
然后构建 resume-assistant:latest 时,是在这个基础上往上叠新层。
所以:
不会把底层完整重复复制一份
多个镜像可以共享同样的基础层
这就是为什么“拉一个基础镜像”不等于浪费一整份重复空间。
———
4. 仓库是什么
仓库就是存放镜像的地方。
比如:
Docker Hub
阿里云 ACR
你这次做的事情就是把基础镜像从 Docker Hub 迁移到阿里云 ACR,方便服务器拉取。
例如:
crpi-.../sunxuehai/python-base:3.11-slim
这里:
crpi-... 是 registry 域名
sunxuehai/python-base 是仓库路径
3.11-slim 是 tag
———
刚才发生的事情,按原理拆开
你刚才在服务器执行了:
docker pull crpi-.../sunxuehai/python-base:3.11-slim
它做的事情不是“把一个文件下载到当前目录”,而是:
Docker 去远程仓库找到这个镜像
把镜像的各层下载到本机 Docker 存储
在本机镜像列表中注册这个镜像 tag
后续 build/run 时可以直接复用
所以它更像是:
“导入到 Docker 本地镜像缓存” 而不是 “下载一个独立压缩包到你的 home 目录”
———
为什么以后不需要每次都重新拉
因为 Docker 会先看本地有没有这个镜像层。
如果你已经拉过:
python-base:3.11-slim
那以后再 build:
只要基础镜像没变
本地缓存还在
Docker 通常直接复用,不重新下载。
这就是缓存机制。
———
那 Docker 为什么有时候又会重新拉
因为以下几种情况会触发重新拉:
本地没有这个镜像
你手动删掉了它
你换了 tag
远程镜像更新而你要求刷新
构建环境不是同一台机器
所以“是否重新拉”取决于:
本地缓存是否存在
镜像标识是否一致
你有没有主动刷新
———
为什么镜像和容器都会占空间
这是新手最容易混的地方。
镜像占空间
因为镜像层要存储:
Python
系统层
pip 包
代码
容器也占空间
因为容器运行后会产生:
可写层
日志
临时数据
所以后面磁盘管理一般要区分:
镜像占多少
容器占多少
volume 占多少
build cache 占多少
———
你接下来最应该建立的意识
Docker 不是“每次执行一条命令就产生一个没法控制的大黑盒”,而是一个有状态的本地系统:
pull 是把镜像放进本地镜像缓存
build 是基于已有层构造新镜像
run 是基于镜像创建容器
push 是把镜像上传到远程仓库
rm/rmi/prune 是清理本地状态
———
对你当前项目,这个理解具体落到什么地方
现在服务器上已经有一个基础镜像:
crpi-.../sunxuehai/python-base:3.11-slim
接下来如果我们 build 项目镜像:
resume-assistant:latest
它不会从零开始,而是:
以 python-base:3.11-slim 为底座
再安装 requirements
再复制项目代码
生成新的业务镜像
所以基础镜像不是垃圾,而是后续构建的“底座缓存”。
———