资讯详情

Docker核心概念:镜像、容器、仓库的区别与底层逻辑

📅 2026/10/7 3:54:33 | 华诺云谱 👁 阅读
Docker核心概念:镜像、容器、仓库的区别与底层逻辑
很多人跑来问我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 联合文件系统与层LayerDocker镜像由多个只读层叠加而成。每一条构建指令如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隔离的对象包括进程PIDPID Namespace、网络栈Network Namespace、挂载点Mount Namespace、主机名UTS Namespace、IPC进程间通信、用户IDUser 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可以理解为“整个应用商店”。RepositoryRegistry内部某个具体的项目命名空间比如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_PASSWORD123456 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显式指定用户。说句心里话镜像、容器、仓库这三个概念真正让我觉得“懂”了的瞬间并不是看完一篇长文而是在一次线上迁移中亲手把几十个镜像从旧服务器导出、推到新仓库、再在新机器上拉取跑通的时候。概念是地图命令是脚印只有把两者叠起来走一遍地图才算被真正记住。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑