☰
Docker核心概念:镜像、容器、仓库的区别与底层逻辑
2026/10/7 3:54:32 网站建设 项目流程

很多人跑来问我Docker的镜像、容器、仓库到底怎么区分,我每次都要花两分钟解释一遍“镜像不是系统盘”“容器不是虚拟机”“仓库不是代码仓库”。其实这三个概念只要找准了参照物,理解起来非常快。我写这篇就是想一次性把这个事说透,看完你能清楚说出三者的分工、各自的底层逻辑,以及日常操作里常见的坑到底出在哪。适合刚入门Docker、背书背得一头雾水、或者用过命令但一直没把概念串起来的同学。

1. 三条一句话:镜像、容器、仓库的“分工”与“协作”

1.1 镜像:只读的标准化模板

镜像这个词很容易让人想到“系统的镜像文件”,比如给电脑重装系统时用的ISO。确实有点像,但Docker镜像更准确的理解是“带运行环境的应用程序模板”。

一个镜像里打包了应用本身、运行所需的环境变量、依赖库、配置文件和默认启动命令。比如nginx镜像,里面就是nginx程序加它的默认配置、依赖库。但这里面没有内核,也没有操作系统内核,只有一套完整的用户态文件系统。这也是镜像能做小、能秒级启动的根本原因。

镜像还有一个很关键的特性:只读。你运行容器时,Docker不会直接往镜像里写文件,而是“从镜像启动一个新环境,临时层交给容器”。这个只读特性保证了同一个镜像拉取到任何机器上,启动出来的环境都是一致的。这一点对“环境一致性”至关重要,也是镜像能作为分发标准的原因。

1.2 容器:模板的可运行实例

容器是镜像的运行态,是“镜像被真正启动起来之后的那个环境”。你可以把镜像理解为类(Class),容器就是根据类创建出来的对象(Instance)。同一个镜像可以启动多个容器,就像同一个类可以new出多个对象一样,彼此独立,互不干扰。

容器本身是一个受隔离的进程环境,里面运行着你指定的主进程。它有自己独立的文件系统视图、网络栈、进程命名空间等。你在容器里看到的一套“小系统”实际上只是宿主机的若干进程,被命名空间隔离出来的一个视角。这一点后面我会专门展开,但先记住:容器不是虚拟机,它没有自己的内核,它用的是宿主机的内核,只是把用户态隔离了。

1.3 仓库:镜像的“应用商店”

仓库是集中存储、分享镜像的地方。Docker Hub是官方公共仓库,里面放着nginx、mysql、redis这些常用镜像。你要运行一个软件,第一步通常是docker pull直接从仓库里拉镜像到本地。

仓库在这里扮演的角色就是分发中心。代码有代码仓库,镜像有镜像仓库,本质都是“制品存储与版本管理”。很多初学者会把“仓库”(Registry)和“镜像仓库里的某个项目”(Repository)搞混,后面我会用Maven仓库来类比解释。

到这里只用了三句话,但你已经能回答“镜像、容器、仓库分别是什么”了:镜像是有只读模板,容器是镜像跑起来的实例,仓库是存放和分发镜像的地方。

2. 镜像底层不是黑盒:分层存储与Dockerfile的对应关系

很多人用Docker但是从来不看镜像里面长什么样,我建议你至少做一次“拆开观察”的动作。理解镜像的分层存储,对你排查体积问题、理解缓存机制、甚至修复一些莫名其妙的启动报错都帮助很大。

2.1 联合文件系统与层(Layer)

Docker镜像由多个只读层叠加而成。每一条构建指令(如Dockerfile里的RUN、COPY)通常会生成一个新的层。所有层通过联合文件系统(OverlayFS、AUFS等)合成一个统一的文件系统视图,容器启动时只在这个视图之上加一个可写层。

打个比方:镜像像一本印好的书,每页都是只读的;容器像在书上叠了一张可擦写的透明纸,你可以在纸上写字、画线,但永远不会改动书页本身。合上透明纸重新拿一本新书,内容还是原来那样。

这个设计带来的第一个好处是存储节省:多个镜像如果共享基础层(比如都基于某个同一个基础镜像),宿主机上只需要保存一份底层文件。我常跟同事说,你拉十个别的基础镜像,磁盘用量不会简单变成十倍,因为底层共享了一大半。

第二个好处是构建缓存。Docker构建镜像时,如果某个层之前已经构建过且对应指令没变,会直接复用缓存层,不重复执行。你的Dockerfile写得是否“有利于缓存命中”(把容易变化的COPY放后面,把包安装放前面),会直接影响每次构建时间。

2.2 为什么镜像能够“瘦小”却功能完整

新手常问:一个几百MB的镜像,装得下完整的操作系统吗?答案是它根本不需要完整的操作系统。Docker镜像只包含运行指定应用所需的最小子集:用户态程序、动态链接库、配置文件、时区数据等。内核由宿主机提供。

所以基于同一个基础镜像的容器,在不同机器上跑出来的“系统视角”可能不一样的是内核模块、系统调用、设备驱动等接近内核的部分,而用户态是完全一致的。这也是为什么有些镜像要求宿主机有特定内核模块(比如overlay、iptables),而另一些镜像换个内核就跑不了。

你可以用docker history 镜像名查看一个镜像是怎么一层层砌起来的。看到每一层的体积、创建命令,你就能体会“一层指令一层层”的含义。我有一次发现镜像体积莫名变大,就是靠docker history定位到某条RUN命令把整个目录拷贝了进去,后来改成多阶段构建才解决。

2.3 镜像与容器快照的边界:可写层

当你启动容器,文件系统变成了“镜像只读层+容器可写层”。你在容器里写入的所有文件都落在可写层。容器一旦被删除,可写层也会跟着消失,这就是为什么“不下持久化措施就删容器会丢数据”的原因。

但要记住:容器停止不等于容器删除。停止后的容器,可写层仍在磁盘上,你可以重新docker start,状态和数据都还在。只有docker rm才会彻底删掉容器和它的可写层。

另外还有一个容易混淆的操作:docker commit可以把一个容器的当前状态(包括可写层修改)打包成新镜像。很多教程说“不要滥用commit,要写Dockerfile”,这是对的,因为commit产生的镜像不可复现,且不知道改了哪些层。但它在快速从“手动调好的容器”转为镜像时非常有用,尤其是排查问题时临时保存现场。

3. 容器不是小虚拟机:Namespace与Cgroup到底隔离了什么

3.1 容器里的进程看到的“隔离世界”

你进入一个容器执行ps -ef,看到进程PID往往不是从1开始就是很小的数字,而且进程列表里“只有自己”,看不到宿主机其他进程。这是因为Docker通过Linux Namespace给容器创建了独立的空间。

Namespace隔离的对象包括:进程PID(PID Namespace)、网络栈(Network Namespace)、挂载点(Mount Namespace)、主机名(UTS Namespace)、IPC(进程间通信)、用户ID(User Namespace)等。隔离的效果是:容器内的进程以为自己在独占一台机器,实际上它的视角只是宿主机全局视角的分割片段。

例如PID Namespace里,容器内进程的PID 1是容器主进程,但在宿主机上看可能是12345。这种“同一个进程在不同命名空间里呈现不同PID”的现象,是理解容器隔离的关键。

很多“容器资源隔离”话题就是从这里来的。隔离不是性能虚拟化,而是“视图隔离+资源限制”。容器内的进程和宿主机其他进程共享同一个内核,但看不见对方,并且能限制自己能吃多少资源。

3.2 资源限制:Cgroup如何控制CPU和内存

Namespace负责“看得见什么”,Cgroup负责“能用多少”。Cgroup可以对进程组做CPU、内存、磁盘IO、网络带宽的配额限制。

默认情况下你运行一个容器,如果不加任何限制,它能使用的CPU和内存上限受制于宿主机整体资源;而你加了-m、--cpus参数后,容器进程的资源就会被约束在指定范围内。这也是生产环境里防止某个容器把整台机器打满的标准做法。

我见过一个比较典型的案例:同一台宿主机跑了十几个容器,其中一个服务内存泄漏,由于没设限制,宿主机内存被吃满导致其他所有容器一起卡死。后来给每个容器都加了--memory、--memory-swap和--cpus限制,整机稳定性立刻上来了。本质上,容器之间只是“隔离的邻居”,如果不对资源做限制,一个坏邻居能拖垮整栋楼。

3.3 容器生命周期:从Run到Rm的完整动作

容器不是一直存在的,它有明确的生命周期状态。常用命令的对应关系我整理出来,方便你对照概念理解:

状态典型命令说明
创建docker create从一个镜像创建容器,但不启动
运行docker run创建+启动一次完成
停止docker stop优雅结束容器主进程
冻结docker pause暂停容器内所有进程
删除docker rm删除停止状态容器,及可写层
进入docker exec -it在运行中容器内执行新进程

我第一次用Docker时,看到docker run后面跟着一堆参数以为这是“安装软件的完整过程”,其实docker run的本质是“基于镜像创建并启动一个容器实例”。

再提一个和容器生命周期相关的踩坑:很多人用docker stop发现容器明明停了,但docker ps -a里还能看到,就以为出问题了。其实这只是Docker保留了“停止状态的容器”,方便你查看日志或重新启动。你如果不想要这个容器了,记得docker rm,否则系统里会堆积一堆僵尸容器,占用的可写层磁盘空间会越来越大。

4. 仓库配置里的门道:从Docker Hub到国内加速源

4.1 Registry、Repository与Tag的命名规则

中文里“仓库”一个词对应了两个英文概念,这是一个典型的翻译歧义点。

  • Registry:镜像服务的整个存储后端,比如Docker Hub、阿里云镜像仓库、你的私有Harbor,可以理解为“整个应用商店”。
  • Repository:Registry内部某个具体的项目命名空间,比如library/nginx里的nginx,相当于“应用商店里的某个店铺”。
  • Tag:镜像的具体版本标签,比如nginx:1.25和nginx:latest,相当于“店铺里某个版本的货”。

完整的镜像名通常写作[Registry]/[Repository]:[Tag]。如果你省略Registry,默认就是Docker Hub;如果省略Tag,默认是latest。理解这个命名规则后,很多看起来奇怪的镜像地址就不难读了,比如docker.io/library/nginx:1.25就是Docker Hub上nginx项目的1.25版本。

4.2 国内镜像加速器配置(阿里云为例)

Docker Hub虽然在国内能访问,但经常慢到怀疑人生。几年前的普遍做法是给Docker配置镜像加速器(Mirror),把Docker Hub的拉取流量引导到国内的缓存节点。

以阿里云容器镜像服务为例,登录控制台后可以拿到一个专属加速地址,形如https://xxx.mirror.aliyuncs.com。然后修改Docker守护进程配置,在/etc/docker/daemon.json里加一行:

{ "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"] }

改完重启Docker服务再docker pull,速度立竿见影。注意,镜像加速只影响从Docker Hub拉取公开镜像,不影响docker push到自己的仓库,也不影响直接docker pull指定私有仓库的镜像。

这里有个容易被忽略的细节:如果改成加速源之后遇到“拉下来的镜像和官方不一致”“manifest列表查不到”之类的问题,很可能是加速节点的缓存过期或者同步故障。此时可以用docker pull docker.io/library/nginx:1.25强制走官方源对比一下。

4.3 私有仓库与离线分发场景

除了公共仓库,实际工作中更常用私有仓库。小团队可以直接用Docker官方提供的registry镜像起一个私有仓库:

docker run -d -p 5000:5000 --name registry registry:2

之后给镜像打上私有仓库地址的标签并推送:

docker tag myapp:latest localhost:5000/myapp:latest docker push localhost:5000/myapp:latest

这个过程完美演示了“仓库”作为镜像流转中枢的价值:你在一台机器上构建好镜像,推到私有仓库,另一台机器拉下来运行,整个分发链路不需要传输文件包,只传输镜像层数据。

离线场景也有类似逻辑。内网环境无法访问外部仓库时,可以在一台有网的机器上docker pull所有需要镜像并docker save成tar包,再拷入内网用docker load导入。此时docker save和docker load扮演的是“介于仓库和本机之间的传输桥梁”。

顺带一提,很多人看到Maven里“配置阿里云仓库”就想当然以为Docker也一样。两者都是“仓库”概念,但一个是Java构件的存储中心,一个是容器镜像的存储中心。配置思路有相似之处(都是改一个配置指向国内源),但文件格式、工具链完全不同。用这个类比去理解“仓库是制品中心”的大方向可以,别混着照搬命令就好。

5. 一次打通三者的最小实验:从MySQL镜像到运行容器再回传仓库

只讲概念不讲流程等于白讲。我建议你亲手跑一遍下面这个最小闭环,做完之后镜像、容器、仓库三个概念就彻底粘在一起了。

5.1 拉取镜像与运行容器的关联

我以MySQL为例,因为很多人学习Docker时都会尝试“用Docker装MySQL”,热搜里经常看到“docker安装mysql失败”。先执行:

docker pull mysql:8.0

这一步验证的是“仓库→镜像”的流转。docker pull之后可以用docker images看到本机已经有了这个镜像。

然后运行一个容器:

docker run -d --name my-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

这一步验证的是“镜像→容器”的实例化。-e传入环境变量,-p映射端口。注意MySQL容器默认端口是3306,如果宿主机3306已经被本地MySQL占用,换一个宿主机端口,比如-p 3307:3306。

“docker安装mysql失败”最常见的原因就是这种端口冲突、数据目录权限不足、容器启动后立即退出。排查方式通用:先看docker logs my-mysql输出,再检查端口是否被占用、卷权限是否为mysql:mysql。

5.2 修改容器并生成新镜像

现在进入容器做点修改,制造一个“容器状态与镜像不同”的场景:

docker exec -it my-mysql bash

进入后随便创建一个数据库,然后退出。此时容器可写层里多了一些数据。如果这时候把容器docker commit成新镜像:

docker commit my-mysql my-mysql-with-db:0.1

你就完成了“容器→新镜像”的转换。这个新镜像包含了刚才创建数据库的修改,但它不是通过Dockerfile构建出来的,别人无法复现你手动操作的过程。所以在正式场景里我强烈不建议把这种镜像往生产推,但用来理解“容器可写层与镜像层”的关系非常直观。

5.3 push回自己的仓库:打通闭环

最后一步是把新镜像推到你的仓库。如果你有Docker Hub账号,可以这样:

docker tag my-mysql-with-db:0.1 yourname/my-mysql-with-db:0.1 docker login docker push yourname/my-mysql-with-db:0.1

如果想在本地模拟一套私有仓库流程,就跑一个registry容器,把tag改成localhost:5000/my-mysql-with-db:0.1再push。

做完这一圈之后,你的脑子里应该能画出一条链路:别人把镜像推到仓库,你从仓库拉镜像,镜像启动后变成容器,容器可写层修改后再提交成新镜像,新镜像又能推回仓库供其他人拉取。镜像、容器、仓库在这里不再是三个孤立名词,而是同一个流转闭环的三个角色。

6. 我最想纠正的四个概念误区

6.1 “容器就是轻量级虚拟机”

这个误区影响最深。容器和虚拟机都能隔离环境、都有独立的文件系统和网络栈,但底层机制完全不同:虚拟机有独立的Guest OS和虚拟化硬件,容器直接共享宿主机内核。

这个区别带来两个实际影响。一是性能,容器启动通常是毫秒级,虚拟机是秒级;容器内进程性能开销极小,虚拟机有虚拟化损耗。二是安全边界,容器隔离的是“用户态视图”,不隔离内核,所以容器逃逸到宿主机内核的风险比虚拟机高。不要把容器当中虚拟机来构建强隔离环境,这是我在生产环境里反复强调的。

6.2 “容器删了应用就没了,数据也会丢”

应用没了是真的(容器删了容器内的文件就没了),但数据是否丢失取决于你有没有用数据卷(Volume)或绑定挂载。

正确做法是把数据库、日志这类需要持久化的数据放在卷里。比如MySQL的-v /my/data:/var/lib/mysql就是把宿主机目录挂载到容器内数据目录。这样即使容器被删除,宿主机目录里的数据依然保留,新的容器挂载同一个目录就能接续使用。

理解这一点后就能解释另一个常见问题:为什么很多镜像升级后“数据还在”,不是因为容器内写层还在,而是因为数据卷独立于容器生命周期,Docker不会因为删除容器而删除卷,除非你同时加了-v参数强制删除关联卷。

6.3 “仓库和镜像源是一回事”

很多新手以为“配置了国内镜像加速器就等于配置了仓库切到国内”,其实这是两件事。镜像加速器只影响从Docker Hub拉取时的网络路径,你的镜像仍然来自Docker Hub的镜像仓库;而“使用某个私有仓库”是直接把镜像来源从Docker Hub切换到另一个Registry。

这就像你在京东买东西,把快递中转点换到本地缓存站,但你买的货还是从京东仓库发出的。如果你希望私有化运营镜像,自己搭Harbor或使用云厂商的镜像仓库服务,那才是真正换了一个“仓库”。

6.4 “用了Docker就不需要学系统知识”

Docker确实屏蔽了很多环境配置,但它没有帮你抹掉内核、权限、网络、存储这些底层知识。你在排查“容器内权限不足”“容器无法访问外部网络”“挂载目录像变了个样”“时区不对”等问题时,最终都要回到Linux基础。

就拿目录挂载举例:容器内运行程序的用户Uid,与宿主机挂载目录的属主Uid如果不匹配,就会出现“明明宿主机文件存在,容器里写不进去”的诡异现象。这不是Docker的bug,而是Unix权限模型在起作用。理解了这一点,你自然就明白为什么很多官方镜像要求挂载目录所有者为特定Uid。

我早期因为不懂这个踩过很多坑,后来总结出的办法是:拉镜像后第一件事看官方文档里说明的默认用户和Uid,挂载目录统一用chown调整属主,或者用docker run --user显式指定用户。

说句心里话,镜像、容器、仓库这三个概念,真正让我觉得“懂”了的瞬间,并不是看完一篇长文,而是在一次线上迁移中亲手把几十个镜像从旧服务器导出、推到新仓库、再在新机器上拉取跑通的时候。概念是地图,命令是脚印,只有把两者叠起来走一遍,地图才算被真正记住。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询