三驾马车:镜像、容器与仓库 —— 搞懂Docker最核心的三个概念
Docker的整个世界,归根结底就围绕着三个东西在转:镜像、容器、仓库。理解了它们的关系,你就掌握了Docker的骨架。
📋 开篇自测:你已经知道多少?
- 你能解释镜像和容器之间的关系吗?类比成什么最合适?
- 镜像的”分层存储”是怎么回事?它带来了什么好处?
- Docker Hub和私有镜像仓库各自适用什么场景?
一、镜像:应用的”冻干标本”
如果你喝过冻干咖啡,你就能理解镜像是什么。
冻干咖啡的制作过程是:先煮好一壶新鲜咖啡,然后通过冷冻干燥技术把水分全部去掉,变成粉末状的咖啡。这个粉末可以长期保存、方便运输,需要喝的时候加热水冲泡就行——还原出来的咖啡和原来那壶味道几乎一样。
Docker镜像就是你的应用程序的”冻干标本”。它把应用的代码、运行环境、依赖库、配置文件等一切运行所需的东西,打包成一个静态的、不可变的文件。这个文件可以存储、传输、分享,需要运行的时候”加水还原”——也就是用它创建一个容器。
镜像的分层结构
镜像有一个精妙的设计:分层存储(Layered Storage)。
想象你在叠三明治。最底下一层是面包(基础操作系统,比如Ubuntu),上面放一层生菜(安装Python运行环境),再放一层番茄(安装项目依赖),最上面放一片芝士(复制你的应用代码)。每一层都是独立的,而整个三明治就是你的完整镜像。
这种设计带来了巨大的好处:
共享层:如果你有两个项目都基于Ubuntu + Python,那么Ubuntu层和Python层只需要存储一份。就像两个三明治可以共用同一片面包——当然实际上它们各有各的面包,但在Docker的世界里,相同的层确实只需要下载和存储一次。
增量更新:你修改了应用代码,重新构建镜像时只有代码那一层会变化,其他层直接复用。就像你只换了三明治最上面的芝士,不需要把整个三明治重做。
构建缓存:Docker在构建镜像时会自动缓存每一层。如果某一层的内容没有变化,就直接用缓存,大大加速了构建过程。
我们可以用命令来验证这种分层结构:
# 拉取一个镜像
docker pull nginx
# 查看镜像的分层详情
docker history nginx
你会看到类似这样的输出,每一行就是一层:
IMAGE CREATED CREATED BY SIZE
a8758716bb6a 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago COPY file:xxx in /docker-entrypoint.d 4.62kB
<missing> 2 weeks ago COPY file:xxx in /docker-entrypoint.d 3.02kB
<missing> 2 weeks ago /bin/sh -c set -x && groupadd... 112MB
<missing> 2 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.8.x 0B
...
镜像的命名规则
镜像的名称遵循一套标准的命名规范,理解它有助于你在使用中不犯迷糊:
[仓库地址/]仓库名[:标签]
举几个例子:
| 完整名称 | 仓库地址 | 仓库名 | 标签 |
|---|---|---|---|
nginx | Docker Hub(默认) | nginx | latest(默认) |
nginx:1.28 | Docker Hub | nginx | 1.28 |
ubuntu:22.04 | Docker Hub | ubuntu | 22.04 |
mycompany/webapp:v2 | Docker Hub | mycompany/webapp | v2 |
registry.cn-hangzhou.aliyuncs.com/myns/myapp:1.0 | 阿里云仓库 | myns/myapp | 1.0 |
标签(Tag) 是一个非常重要的概念。同一个软件可以有多个版本,每个版本对应不同的标签。如果你不指定标签,默认就是 latest,但请注意——latest 不一定是最新版本!它只是一个约定俗成的默认标签,实际指向哪个版本取决于镜像维护者。
在生产环境中,永远不要用 latest 标签。因为它可能在你不知情的情况下被更新为新版本,导致你的应用行为发生变化。应该始终使用具体的版本标签,比如 nginx:1.28.3。
常用镜像操作
# 搜索镜像
docker search redis
# 拉取镜像(默认从Docker Hub)
docker pull redis:7.2
# 查看本地所有镜像
docker images
# 查看镜像详细信息
docker inspect nginx
# 给镜像打标签(相当于起别名)
docker tag nginx:latest my-nginx:v1
# 删除镜像
docker rmi nginx:1.28
# 导出镜像为文件(方便离线传输)
docker save -o nginx.tar nginx:latest
# 从文件导入镜像
docker load -i nginx.tar
🤔 想一想 为什么Docker选择了分层存储而不是把整个镜像做成一个单一的大文件?如果是单一文件,会有哪些问题?
二、容器:镜像的”活体分身”
如果镜像是冻干咖啡粉,那容器就是你冲出来的那杯咖啡——热气腾腾的、活生生的、有生命周期的。
更精确地说:容器是镜像的一个运行实例。从同一个镜像可以创建出任意多个容器,就像同一个菜谱可以做出无数份同样的菜。每个容器都是独立的,在一个容器里的修改不会影响其他容器,也不会影响原始镜像。
容器的生命周期
一个容器从生到死,会经历以下几个状态:
创建(Created) → 运行(Running) → 暂停(Paused) → 停止(Stopped) → 删除(Removed)
就像一个人的一天:起床(创建/启动)→ 工作(运行)→ 午休(暂停)→ 下班回家(停止)→ 第二天换了个人来上班(删除旧容器、创建新容器)。
对应的命令:
# 创建并启动容器
docker run -d --name my-redis redis:7.2
# 暂停容器(进程被冻结,但不释放资源)
docker pause my-redis
# 恢复容器
docker unpause my-redis
# 停止容器(发送SIGTERM信号,等待优雅退出)
docker stop my-redis
# 强制停止(发送SIGKILL信号,立即终止)
docker kill my-redis
# 重新启动已停止的容器
docker start my-redis
# 删除容器(必须先停止)
docker rm my-redis
# 强制删除运行中的容器
docker rm -f my-redis
容器的可写层
这里有一个关键的技术细节需要理解:容器在镜像的只读层之上添加了一个可写层。
还是用三明治来比喻。镜像是一个做好的三明治(只读的,不能修改),容器启动时相当于在这个三明治最上面加了一层保鲜膜。你在容器里做的所有修改——创建文件、安装软件、修改配置——都写在这层保鲜膜上。原始的三明治一点都没被动过。
这意味着:
- 删除容器 = 扔掉保鲜膜。你在容器里做的所有修改都消失了。
- 原始镜像不受影响。你可以继续用它创建新的容器。
- 如果你想保留修改,需要用
docker commit把当前容器的状态保存为一个新镜像(但这不是推荐做法,后面会讲更好的方式)。
# 把容器的当前状态保存为新镜像
docker commit my-container my-new-image:v1
与运行中的容器交互
容器跑起来之后,你经常需要”进去看看”:
# 进入容器的交互式终端
docker exec -it my-redis bash
# 如果容器里没有bash,可以试试sh
docker exec -it my-redis sh
# 在容器里执行单条命令(不进入交互模式)
docker exec my-redis redis-cli ping
# 查看容器日志
docker logs my-redis
# 实时追踪日志(类似tail -f)
docker logs -f my-redis
# 查看容器资源使用情况
docker stats my-redis
# 查看容器内的进程列表
docker top my-redis
# 把容器内的文件复制到宿主机
docker cp my-redis:/etc/redis/redis.conf ./redis.conf
# 把宿主机文件复制进容器
docker cp ./my-config.conf my-redis:/etc/redis/redis.conf
⚠️ 常见误区
- 误区一:“容器应该像虚拟机一样长期运行”。容器的设计哲学是”用完即弃”。与其修修补补一个旧容器,不如直接用新镜像创建一个新的。
- 误区二:“docker commit是构建镜像的好方法”。不是。commit产生的镜像缺乏可重复性和透明度。应该用Dockerfile来构建镜像(第4章会详细讲)。
- 误区三:“一个容器里应该跑多个服务”。每个容器应该只运行一个进程(或者说一个关注点)。需要多个服务协作时,应该用多个容器配合(第7章会讲Docker Compose)。
三、仓库:镜像的”应用商店”
你的手机上有App Store或Google Play用来下载App。Docker世界里也有类似的东西,叫做镜像仓库(Registry)。
Docker Hub:公共大集市
Docker Hub(https://hub.docker.com)是Docker官方运营的公共镜像仓库,也是全世界最大的容器镜像集散地。你能在上面找到几乎所有主流开源软件的官方镜像:
- 操作系统:ubuntu, alpine, debian, centos
- 编程语言:python, node, golang, openjdk
- 数据库:mysql, postgres, redis, mongodb
- Web服务器:nginx, httpd, caddy
- 消息队列:rabbitmq, kafka
- …
Docker Hub上的镜像分为两种:
官方镜像(Official Images):由Docker或软件官方团队维护,质量有保证,名称前面没有斜杠(如 nginx, python, redis)。在搜索结果里会有”Official”徽章。
社区镜像(Community Images):由个人或第三方组织上传,名称格式为 用户名/镜像名(如 bitnami/redis, linuxserver/nginx)。质量参差不齐,使用前建议检查下载量和更新频率。
# 在命令行搜索镜像
docker search nginx
# 拉取官方Nginx镜像
docker pull nginx
# 拉取某个社区版本的镜像
docker pull bitnami/nginx
私有仓库:自家的保险柜
企业内部的代码和镜像不适合放在公共平台上。这时候就需要搭建私有镜像仓库。
最简单的方案是Docker官方提供的Registry镜像,一条命令就能跑起来一个私有仓库:
# 启动一个本地私有仓库
docker run -d -p 5000:5000 --name my-registry registry:2
# 给镜像打上私有仓库的标签
docker tag nginx:latest localhost:5000/my-nginx:v1
# 推送到私有仓库
docker push localhost:5000/my-nginx:v1
# 从私有仓库拉取
docker pull localhost:5000/my-nginx:v1
在生产环境中,更常用的私有仓库方案有:
- Harbor:VMware开源的企业级仓库,支持安全扫描、访问控制、镜像签名等高级功能
- AWS ECR:亚马逊云的容器镜像服务
- 阿里云容器镜像服务:国内常用的选择
- GitHub Container Registry:与GitHub深度集成
镜像加速
如果你在国内,直接从Docker Hub拉取镜像可能会很慢甚至超时。配置镜像加速器可以显著提升下载速度。
在Docker Desktop中:打开Settings → Docker Engine,在JSON配置中添加可用的镜像源地址:
{
"registry-mirrors": [
"https://你的镜像加速地址"
]
}
在Linux中,编辑 /etc/docker/daemon.json:
{
"registry-mirrors": [
"https://你的镜像加速地址"
]
}
然后重启Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
💡 提示:国内 Docker Hub 镜像加速服务变动频繁,过去常用的阿里云、腾讯云、网易等公共加速地址可能已停止服务或限制访问。建议搜索”Docker 国内镜像加速 + 当前年份”获取最新可用的镜像源列表。部分云服务商仍为自家云主机用户提供内网加速服务,可查阅对应云服务商的文档。
🤔 想一想 如果你是一家初创公司的技术负责人,你会选择Docker Hub的付费方案还是自建私有仓库?两种方案的成本和维护负担分别是什么?
四、三者的关系:一图胜千言
让我们用一张关系图把三个核心概念串起来:
┌─────────────┐
│ 仓库 │
│ (Registry) │
└──────┬──────┘
push↑ ↓pull
┌──────┴──────┐
│ 镜像 │
│ (Image) │
└──────┬──────┘
run ↓ ↑ commit
┌────────────┼────────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 容器 A │ │ 容器 B │ │ 容器 C │
│(Container)│ │(Container)│ │(Container)│
└──────────┘ └──────────┘ └──────────┘
用一句话概括它们的关系:仓库是存放镜像的地方,镜像是创建容器的模板,容器是镜像运行起来的实例。
再用一个日常类比:仓库就像书店,镜像就像一本书的模板(母版),容器就像你买回家的那本书。书店里有各种各样的书(仓库里有各种镜像),你选了一本买回家(pull镜像),然后可以在上面做笔记、贴便签(容器里的可写层)。即使你把自己那本书写满了笔记,书店里的原版不受影响。
动手验证
让我们通过一系列命令来亲眼看看三者的互动:
# 从仓库拉取镜像
docker pull alpine:3.19
# 查看本地镜像列表
docker images
# 从镜像创建并运行3个独立的容器
docker run -d --name box1 alpine:3.19 sleep 3600
docker run -d --name box2 alpine:3.19 sleep 3600
docker run -d --name box3 alpine:3.19 sleep 3600
# 查看运行中的容器
docker ps
# 在box1里创建一个文件
docker exec box1 sh -c "echo 'hello from box1' > /tmp/greeting.txt"
# 验证box2里没有这个文件
docker exec box2 sh -c "cat /tmp/greeting.txt"
# 输出:cat: can't open '/tmp/greeting.txt': No such file or directory
# 清理
docker rm -f box1 box2 box3
这个实验直观地证明了:从同一个镜像创建的多个容器是完全独立的,在一个容器里做的修改不会影响到其他容器。
📝 掌握度自测
-
用自己的话解释什么是Docker镜像的”分层存储”,以及它带来了哪些好处?
-
docker run和docker start有什么区别?docker stop和docker kill又有什么区别? -
为什么不推荐在生产环境中使用
latest标签?应该怎么做? -
容器的”可写层”是什么?删除容器后,可写层里的数据还在吗?如何持久化保存数据?(可以先思考,答案在第6章)
-
简述Docker Hub上”官方镜像”和”社区镜像”的区别,以及你在选择社区镜像时会关注哪些因素。
💡 自我评估
- 全部答对:你已经牢牢掌握了Docker的三大核心概念!接下来可以进入进阶篇,学习如何构建自己的镜像了。
- 答对3-4题:对核心概念的理解已经相当不错。建议动手多跑几遍本章的命令示例。
- 答对1-2题:三驾马车的关系图值得多看几遍。建议回到”三者的关系”部分,结合动手实验加深理解。
购买课程解锁全部内容
告别「在我电脑上能跑」:Docker 容器化实战
¥29.90