Docker 的三个核心概念:镜像、容器与仓库
说实话,很多人刚开始学 Docker 的时候,都会被这三个概念绕晕。我刚开始接触那会儿,也花了整整一周才彻底搞清楚它们之间的关系。你想想看,镜像、容器、仓库——听起来像是什么物流系统似的。
但说白了,这三个概念就是 Docker 世界的全部基石。搞懂了它们,后面的路就顺了。
镜像(Image)—— 你的“只读模板”
镜像是什么?我的理解是:它是一个只读的、不可变的文件系统快照。
你可以把它想象成一个光盘或者一个安装包。里面装着你的操作系统、软件、依赖、配置文件、环境变量——你的 MaxQuant 流程所需要的一切。
镜像的一个关键特性是分层(Layer)。嗯,这个设计很妙。每一层其实就是一个文件变更的记录。比如我建一个 Ubuntu 基础层,然后装 Java 生成一层,再装 MaxQuant 又生成一层。这样做的好处是,如果别人已经有一个 Ubuntu 镜像,你只需要拉取多出来的那几层就行了,省带宽省时间。
举个例子,一个构建好的 MaxQuant 镜像大概包含这些层:
FROM ubuntu:20.04 # 基础层
RUN apt-get update && apt-get install -y mono-complete # 运行时层
COPY MaxQuant_2.0.3.0 /opt/MaxQuant # 程序层
COPY workflow.conf /etc/maxquant/ # 配置层
这个镜像是只读的。你不能直接改镜像里的东西。你只能通过 Dockerfile 重新构建一个新的镜像。这个特性让我避免了很多环境不一致的坑——我在项目中遇到过好多次同事直接在生产环境里改了文件,结果部署的时候炸了。
核心要点:镜像是不可改变的。每次修改都要重新构建,产生一个新的镜像版本。
容器(Container)—— 镜像的“运行实例”
容器是镜像的可写层实例。意思就是,你从镜像启动一个容器,Docker 会在镜像的顶端加一个可写层,你可以在这个层里做任何事——创建文件、修改配置、运行程序。
容器和镜像的关系,我经常用这个比喻:镜像是一张蓝图纸,容器就是用这张蓝纸造出来的房子。你可以用同一张蓝纸(同一个镜像)造出无数个一模一样的房子(容器),每一栋房子都是独立的。
我刚开始学的时候最困惑的一点是:容器重启后,里面的数据还在吗?
答案是这样的:容器删除后,其可写层也会被删除。也就是说,如果你直接在容器里创建了一些临时文件,容器一删,数据就没了。所以持久化数据一定要用卷(Volume)来挂载到宿主机。
对了,关于容器还有一个很重要的属性:生命周期。容器只有在它内部的主进程运行时才活着。主进程一停,容器就停止了。MaxQuant 分析通常是一个批处理任务——启动容器跑分析,跑完容器自动退出。这在 CI/CD 场景下非常有用。
# 启动一个 MaxQuant 容器,运行分析后自动退出
docker run --rm \
-v /data/raw:/input:ro \
-v /data/results:/output \
maxquant:2.0.3 \
mono /opt/MaxQuant/MaxQuantCmd.exe /input/samples.xml
这里--rm的意思是:容器退出后自动删除。省得你自己手动清理容器。
小技巧:养成使用 --rm 的习惯,特别是跑批任务的时候。不然你的机器上会堆满已经退出的容器,占着磁盘空间。
仓库(Repository)—— 镜像的“存储 & 分发中心”
仓库是存放镜像的地方。你可以把它想象成 GitHub 之于代码,或者 PyPI 之于 Python 包。
Docker 仓库分为两种:
- 公共仓库:最著名的是 Docker Hub。任何人都可以拉取上面的公共镜像,比如 Ubuntu、Mono 等。我建议你用
library/ubuntu这种官方镜像,质量有保证。 - 私有仓库:企业自建的 Registry,或者云服务商提供的(如阿里云 ACR、华为云 SWR)。一般用来存放公司内部封装的镜像,比如你们组封装的 MaxQuant 镜像。
仓库中的每个镜像都有一个 标签(Tag) 来区分版本。标签就是镜像名后面的冒号部分。
# 拉取特定版本的 Ubuntu,标签是 20.04
docker pull ubuntu:20.04
# 构建并打标签
docker build -t maxquant:2.0.3 .
# 推送到私有仓库
docker tag maxquant:2.0.3 myregistry.corp.com/maxquant:2.0.3
docker push myregistry.corp.com/maxquant:2.0.3
避坑指南:我曾经见过有人用 latest 标签作为生产环境镜像的标签。结果某天重新拉取的时候,镜像被覆盖了,导致分析结果出现了不可复现的问题。我建议你永远使用具象的版本号作为标签,比如 2.0.3,而不是 latest。
三者的关系——一张图看清
下面这张图展示了镜像、容器和仓库之间的完整工作流:
它们的区别与联系——一张表格说清
| 维度 | 镜像 | 容器 | 仓库 |
|---|---|---|---|
| 本质 | 只读文件系统快照 | 镜像的运行实例 + 可写层 | 镜像的远程存储 |
| 状态 | 静态(不可变) | 动态(可创建、运行、停止、删除) | 静态(存储介质) |
| 生命周期 | 永久(除非被删除) | 临时(随主进程启动而存在,停止后消亡) | 永久(云端/私有服务器维护) |
| 存储 | 本地磁盘(/var/lib/docker/overlay2) | 镜像层 + 本地可写层 | 远程存储或本地 Registry |
| 数据持久化 | 只读,无法直接修改 | 需挂载卷或绑定数据目录 | 通过 push/pull 同步版本 |
| 一句话总结 | Dockerfile 构建出的产物 | 镜像的运行态 | 镜像的版本管理中心 |
实际场景对照——以 MaxQuant 为例
- 构建镜像:你写好 Dockerfile,定义好基础系统(Ubuntu 20.04)、运行时(Mono)、程序(MaxQuant 2.0.3),执行
docker build -t maxquant:2.0.3 .,Docker 逐层构建并缓存,最后生成一个只读镜像保存在本地。 - 推送到仓库:把镜像打上组织标签,推送到私有仓库,这样实验室其他成员也能拉取使用,保证所有人都用同一版本的分析环境。
- 启动容器运行分析:执行
docker run命令,指定输入输出数据卷,MaxQuant 在容器里启动,开始处理质谱数据。容器跑完自动退出并自毁(--rm),不留下任何垃圾。
我的建议:每次分析前,从仓库拉取指定版本的镜像,而不是用本地缓存的镜像。这样能确保你的分析环境与团队其他成员一致,分析结果才具备可复现性。
镜像、容器、仓库这三个概念,是理解 Docker 设计哲学的门槛。我个人习惯把镜像看成“源代码”,容器是“运行时进程”,仓库是“远程 Git 仓库”。搞懂这三者的关系与区别,后面理解 Dockerfile 语法、网络、数据管理等都会轻松很多。