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。

三者的关系——一张图看清

下面这张图展示了镜像、容器和仓库之间的完整工作流:

Docker 核心概念关系图:镜像、容器、仓库 Docker 核心概念关系图 📦 仓库 存储 & 分发镜像 (Docker Hub / 私有 Registry) 📜 镜像 只读模板 / 分层文件系统 (Ubuntu + Mono + MaxQuant) ⚙️ 容器 镜像的运行实例 + 可写层 (独立的分析运行环境) docker pull docker run docker commit docker push 💡 关键关系 • 仓库存储镜像 • 镜像实例化容器 • 容器可提交为新镜像

它们的区别与联系——一张表格说清

维度 镜像 容器 仓库
本质 只读文件系统快照 镜像的运行实例 + 可写层 镜像的远程存储
状态 静态(不可变) 动态(可创建、运行、停止、删除) 静态(存储介质)
生命周期 永久(除非被删除) 临时(随主进程启动而存在,停止后消亡) 永久(云端/私有服务器维护)
存储 本地磁盘(/var/lib/docker/overlay2) 镜像层 + 本地可写层 远程存储或本地 Registry
数据持久化 只读,无法直接修改 需挂载卷或绑定数据目录 通过 push/pull 同步版本
一句话总结 Dockerfile 构建出的产物 镜像的运行态 镜像的版本管理中心

实际场景对照——以 MaxQuant 为例

  1. 构建镜像:你写好 Dockerfile,定义好基础系统(Ubuntu 20.04)、运行时(Mono)、程序(MaxQuant 2.0.3),执行 docker build -t maxquant:2.0.3 .,Docker 逐层构建并缓存,最后生成一个只读镜像保存在本地。
  2. 推送到仓库:把镜像打上组织标签,推送到私有仓库,这样实验室其他成员也能拉取使用,保证所有人都用同一版本的分析环境。
  3. 启动容器运行分析:执行 docker run 命令,指定输入输出数据卷,MaxQuant 在容器里启动,开始处理质谱数据。容器跑完自动退出并自毁(--rm),不留下任何垃圾。

我的建议:每次分析前,从仓库拉取指定版本的镜像,而不是用本地缓存的镜像。这样能确保你的分析环境与团队其他成员一致,分析结果才具备可复现性。

镜像、容器、仓库这三个概念,是理解 Docker 设计哲学的门槛。我个人习惯把镜像看成“源代码”,容器是“运行时进程”,仓库是“远程 Git 仓库”。搞懂这三者的关系与区别,后面理解 Dockerfile 语法、网络、数据管理等都会轻松很多。