03. 镜像、容器、仓库:三个概念打通 Docker 任督二脉

上一篇你已经装好了 Docker,敲下 docker version 看到了那行帅气的 "Server: Docker Engine"。但紧接着问题来了——打开文档,满屏都是 Image、Container、Registry,这三个词翻来覆去地出现,到底是什么意思?它们之间又是什么关系?别慌,这篇文章用大白话把这三个概念一次性讲透,讲完你就能自己动手跑起第一个容器。

▲ 镜像是"光盘"、容器是"跑起来的游戏"、仓库是"应用商店"——记住这三个比喻,Docker 的世界你就懂了一半。


一、先搞懂关系:一个比喻打通全局

在深入技术细节之前,先用一个生活场景建立直觉:

Docker 概念生活类比一句话解释
镜像(Image)📀 一张游戏光盘只读的"安装包",里面打包好了运行所需的一切
容器(Container)🎮 用光盘启动的游戏镜像跑起来后的"活"实例,可以读写、可以操作
仓库(Registry)🏪 Steam 游戏平台存放和分发镜像的"应用商店"

先看一张图,把三者的关系印在脑子里:

▲ 整条链路就两步:从仓库 docker pull 拉镜像,再用镜像 docker run 起容器。

再看一张"教科书版"的关系图:

  ┌──────────────┐
  │   仓库 Registry  │  ← 镜像的"应用商店"(Docker Hub)
  └──────┬───────┘
         │  docker pull(下载镜像)
         ▼
  ┌──────────────┐
  │   镜像 Image   │  ← 只读模板(像一张光盘)
  └──────┬───────┘
         │  docker run(启动容器)
         ▼
  ┌──────────────┐
  │  容器 Container │  ← 运行中的实例(像光盘跑起来的游戏)
  └──────────────┘

核心关系:仓库提供镜像,镜像启动容器。 就这么简单,别想复杂了。


二、镜像(Image):你的程序被"冻"住了

2.1 镜像到底是什么?

镜像就是一个只读的打包模板。它把运行一个程序所需要的所有东西——代码、运行环境、系统库、配置文件——全部冻结成一个"快照"。

用面向对象的话说:镜像就是"类"(Class),它是一个模板,本身不会运行,但你可以用它创建无数个"实例"。

一句话:镜像不是"程序本身",而是"程序的完整说明书 + 全套原料",随时能烤出一模一样的成品。

2.2 镜像不是一坨,而是"千层饼"

Docker 镜像并不是一整个大文件,它由多个只读层叠加而成,这就是所谓的联合文件系统(UnionFS):

▲ 镜像是"千层饼":底层是基础系统,往上叠运行时、依赖、代码。底层越通用,被复用得越多。

┌─────────────────────────┐
│  第 4 层:你的应用代码     │  ← 最上层,最后添加
├─────────────────────────┤
│  第 3 层:Python 依赖包   │
├─────────────────────────┤
│  第 2 层:Python 运行时   │
├─────────────────────────┤
│  第 1 层:Ubuntu 基础系统  │  ← 最底层,最先构建
└─────────────────────────┘

分层的好处是什么?

  • 复用:你拉 10 个基于 Ubuntu 的镜像,底层那一份 Ubuntu 只需要下载一次

  • 快速构建:改了代码但没改依赖?只有最上面那一层需要重建,底下的全部复用

  • 节省空间:多个镜像共享相同的底层

划重点:分层是 Docker 又快又省的"秘密武器",也是后面理解 Dockerfile、多阶段构建的基石。

2.3 动手拉一个镜像

来,实操一下。从 Docker Hub 拉取 Nginx 镜像:

docker pull nginx

输出大致如下:

Using default tag: latest
latest: Pulling from library/nginx
a2abf6c4d29d: Pull complete
a9edb18cadd1: Pull complete
589b7251471a: Pull complete
186b1aaa4aa6: Pull complete
b4df32aa5a72: Pull complete
a0bcbecc962e: Pull complete
Digest: sha256:0d17b565c37bcbd895e9d9231505a320e4b0e3f0c25b3d6e0a8e4d1b6c1a8f
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

每一行 Pull complete 就是一层镜像被下载完成。你看到 6 层,说明这个 Nginx 镜像由 6 个只读层组成——正好印证了刚才讲的"千层饼"。

2.4 查看本地镜像

docker images

输出:

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    605c77e624dd   2 weeks ago   141MB
字段含义
REPOSITORY镜像名(可以理解为"软件名")
TAG标签(可以理解为"版本号")
IMAGE ID镜像的唯一标识(SHA256 哈希缩写)
CREATED创建时间
SIZE镜像大小

2.5 标签(Tag):同一个软件的不同版本

标签是镜像的"版本别名"。同一个软件可以有多个 tag:

# 拉取指定版本
docker pull nginx:1.24
docker pull nginx:1.25-alpine
​
# 再查看
docker images
REPOSITORY   TAG           IMAGE ID       CREATED       SIZE
nginx        1.25-alpine   a8758716bb6a   3 days ago    42.6MB
nginx        1.24          92b11f67642b   1 week ago    142MB
nginx        latest        605c77e624dd   2 weeks ago   141MB

几个关键点:

标签含义
latest默认标签,不代表最新版本,只是"你没指定 tag 时的默认值"
1.24具体版本号,生产环境推荐用这种
alpine基于 Alpine Linux 的精简版,体积小但功能可能略少
slimDebian 精简版,比 alpine 兼容性更好

血泪教训:生产环境千万不要用 latest!因为你不知道哪天 latest 指向了哪个版本,出了问题排查起来非常痛苦。一定要写死版本号,比如 nginx:1.24。


三、容器(Container):镜像"活"过来了

3.1 容器的本质

如果说镜像是"类",那容器就是"实例对象"。

镜像 + 一个可写层 = 容器。

┌─────────────────────────┐
│  可写层(容器专属)        │  ← 容器运行时产生的文件写在这里
├─────────────────────────┤
│  只读层 4:应用代码       │
├─────────────────────────┤
│  只读层 3:依赖包         │  ← 这些层和镜像完全一样
├─────────────────────────┤
│  只读层 2:运行时         │     多个容器共享这些只读层
├─────────────────────────┤
│  只读层 1:基础系统       │
└─────────────────────────┘

每个容器都有自己独立的可写层,你在容器里创建的文件、修改的配置,都写在这一层。容器删除后,这一层也跟着消失(除非你用了数据卷,后面文章会讲)。

一个镜像可以"复制"出无数个互不干扰的容器——就像一张光盘能刻出很多张游戏盘,大家各玩各的。

3.2 用镜像启动容器

docker run -d --name my-nginx -p 8080:80 nginx:1.24

拆解这条命令:

参数含义
run创建并启动一个容器
-d后台运行(detached 模式),不占终端
--name my-nginx给容器起个名字叫 my-nginx(不指定会自动生成一个随机名)
-p 8080:80把宿主机的 8080 端口映射到容器的 80 端口
nginx:1.24用哪个镜像启动

输出:

a1b2c3d4e5f6g7h8i9j0...(一长串容器 ID)

3.3 验证容器在运行

docker ps

输出:

CONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS         PORTS                  NAMES
a1b2c3d4e5f6   nginx:1.24   "/docker-entrypoint.…"   5 seconds ago   Up 4 seconds   0.0.0.0:8080->80/tcp   my-nginx
字段含义
CONTAINER ID容器唯一标识
IMAGE用的哪个镜像
COMMAND容器启动时执行的命令
STATUS运行状态(Up = 正在跑)
PORTS端口映射
NAMES容器名称

3.4 浏览器访问

打开浏览器,访问 http://localhost:8080,你应该能看到 Nginx 的欢迎页面:

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working.

恭喜,你的第一个容器已经跑起来了! 🎉

3.5 docker run vs docker create + docker start

docker run 其实是两步合一:

命令做的事
docker create只创建容器,但不启动
docker start启动一个已创建的容器
docker run= create + start,一步到位

什么时候用分步?当你想在启动前先配置好容器参数的时候:

# 第一步:只创建
docker create --name my-app -p 3000:3000 my-image:latest

# 第二步:检查配置
docker inspect my-app

# 第三步:确认无误后再启动
docker start my-app

日常使用 99% 的场景直接用 docker run 就行,分步操作了解即可,面试可能会考。

3.6 容器的生命周期

create → start → running → stop → exited → rm(删除)
                    ↑                      │
                    └──── start(重启)─────┘

对应命令:

# 停止容器(发送 SIGTERM,优雅关闭)
docker stop my-nginx

# 强制停止(发送 SIGKILL,立刻杀掉)
docker kill my-nginx

# 启动已停止的容器
docker start my-nginx

# 重启容器
docker restart my-nginx

# 删除容器(必须先停止)
docker rm my-nginx

# 强制删除(不管是否在运行)
docker rm -f my-nginx

注意:docker stop 会先发送 SIGTERM 信号,给容器 10 秒的"善后时间",超时才发 SIGKILL。所以一般用 stop 而不是 kill,避免数据丢失。

3.7 查看容器日志

容器跑在后台,出问题了怎么看日志?

# 查看全部日志
docker logs my-nginx

# 实时跟踪(类似 tail -f)
docker logs -f my-nginx

# 只看最近 20 行
docker logs --tail 20 my-nginx

3.8 进入容器内部

# 在容器内开一个交互式终端
docker exec -it my-nginx /bin/bash

进去之后你就像 SSH 到了一台新机器:

root@a1b2c3d4e5f6:/# ls
bin  boot  dev  docker-entrypoint.d  docker-entrypoint.sh  etc  home  ...

root@a1b2c3d4e5f6:/# cat /etc/nginx/nginx.conf
user  nginx;
worker_processes  auto;
...

root@a1b2c3d4e5f6:/# exit  # 退出容器

exec vs attach:docker exec 是开一个新终端进去,退出不影响容器运行;docker attach 是"接管"容器的主进程终端,退出可能导致容器停止。永远用 exec。


四、仓库(Registry):镜像的"应用商店"

4.1 Docker Hub:官方仓库

Docker Hub(hub.docker.com)是 Docker 官方运营的公共镜像仓库,绝大多数开源软件的镜像都能在上面找到:

镜像描述拉取量
nginxWeb 服务器10B+
mysql关系型数据库10B+
redis缓存数据库10B+
pythonPython 运行环境1B+
nodeNode.js 运行环境1B+

4.2 搜索镜像

docker search nginx --limit 5

输出:

NAME                               DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                              Official build of Nginx.                        19000+    [OK]
jwilder/nginx-proxy                Automated Nginx reverse proxy for docker c…     2200+
nginxinc/nginx-unprivileged        Unprivileged NGINX Dockerfiles                  100+
bitnami/nginx                      Bitnami nginx Docker Image                      150+
rapidfort/nginx                    RapidFort optimized, hardened image for NGINX   50+
字段含义
NAME镜像名
DESCRIPTION简介
STARS点赞数(越高越受欢迎)
OFFICIAL[OK] 表示官方维护的镜像

选镜像的原则:优先用 OFFICIAL(官方镜像),其次选 Stars 高的。不知名的镜像可能包含恶意代码或过时依赖。

4.3 拉取指定版本的镜像

# 从 Docker Hub 拉取
docker pull mysql:8.0
docker pull redis:7-alpine
docker pull python:3.11-slim

拉取地址的规则:

[仓库地址/]用户名/镜像名:标签
写法实际含义
nginx:1.24从 Docker Hub 官方库拉取
bitnami/nginx:latest从 Docker Hub 的 bitnami 用户下拉取
registry.cn-hangzhou.aliyuncs.com/xxx/myapp:v1从阿里云私有仓库拉取

4.4 推送自己的镜像

你也可以把自己的镜像推到仓库,让全世界(或团队内部)都能用:

# 1. 先登录 Docker Hub
docker login
# 输入用户名和密码

# 2. 给镜像打上你的用户名前缀
docker tag my-app:latest your-dockerhub-username/my-app:v1.0

# 3. 推送
docker push your-dockerhub-username/my-app:v1.0

4.5 私有仓库

企业场景下不可能把代码推到公网,这时候需要搭建私有仓库:

方案特点
Docker Registry官方提供的轻量私有仓库,一行命令就能跑起来
HarborVMware 开源的企业级镜像仓库,带权限管理、漏洞扫描
阿里云 ACR云厂商托管服务,免运维
AWS ECRAWS 生态首选

后面文章会专门讲 Harbor 的搭建和使用。


五、完整实操:从零跑一个 Nginx 网站

把前面学的串起来,完整走一遍流程:

5.1 拉取镜像

docker pull nginx:1.24

5.2 查看镜像

docker images nginx
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        1.24      92b11f67642b   1 week ago    142MB

5.3 启动容器

docker run -d \
  --name my-website \
  -p 8080:80 \
  nginx:1.24

5.4 确认运行状态

docker ps
CONTAINER ID   IMAGE        COMMAND                  STATUS         PORTS                  NAMES
f3e8a1b2c3d4   nginx:1.24   "/docker-entrypoint.…"   Up 3 seconds   0.0.0.0:8080->80/tcp   my-website

5.5 浏览器验证

访问 http://localhost:8080,看到 Nginx 欢迎页 ✅

5.6 查看日志

docker logs my-website
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
...
2024/01/15 08:30:00 [notice] 1#1: start worker processes
2024/01/15 08:30:00 [notice] 1#1: start worker process 29

5.7 进入容器改一下欢迎页

docker exec -it my-website /bin/bash

在容器内部:

# 修改欢迎页
echo "<h1>Hello from Docker!</h1>" > /usr/share/nginx/html/index.html

# 退出容器
exit

刷新浏览器,页面变成了 "Hello from Docker!" ✅

5.8 清理

# 停止容器
docker stop my-website

# 删除容器
docker rm my-website

# 删除镜像(可选)
docker rmi nginx:1.24

删除顺序很重要:先停容器 → 再删容器 → 最后才能删镜像。容器还"活着"的时候镜像是删不掉的。


六、常用命令速查表

场景命令
拉取镜像docker pull nginx:1.24
查看本地镜像docker images
搜索仓库镜像docker search nginx
删除镜像docker rmi nginx:1.24
创建并启动容器docker run -d --name x -p 80:80 nginx
查看运行中容器docker ps
查看所有容器(含已停止)docker ps -a
停止容器docker stop my-nginx
启动已停止的容器docker start my-nginx
重启容器docker restart my-nginx
进入容器docker exec -it my-nginx /bin/bash
查看容器日志docker logs -f my-nginx
删除容器docker rm my-nginx
强制删除容器docker rm -f my-nginx

七、常见踩坑点

问题原因解决方案
docker pull 超时国内网络访问 Docker Hub 慢配置镜像加速器(阿里云、腾讯云都提供)
端口被占用宿主机 8080 已被其他程序使用换个端口:-p 9090:80
容器启动后立即退出容器内没有前台进程用 -d 时确保程序在前台运行
docker exec 报 "not running"容器已经停了先 docker start,再 exec
删不掉镜像有容器还在用这个镜像先删容器,再删镜像
容器内数据丢失容器删除后可写层也被删了用数据卷(Volume)挂载,后面文章会讲

八、本文要点回顾

概念类比核心要点
镜像 Image光盘 / 类只读模板,分层存储,一个镜像可以创建多个容器
容器 Container游戏 / 对象镜像的运行实例,有自己的可写层,删除后可写层消失
仓库 Registry应用商店存储和分发镜像,Docker Hub 是最大的公共仓库

三个要点带走:

  1. 镜像分层存储是 Docker 高效的秘密——多个镜像共享底层,节省空间和带宽

  2. 生产环境写死 tag,永远不要用 latest

  3. 先停容器再删容器再删镜像,顺序不能乱

金句:镜像负责"描述世界长什么样",容器负责"在世界上真正活着",仓库负责"把世界分享给所有人"。


九、下集预告

你已经能拉镜像、跑容器了。但每次都要敲一长串 docker run 参数,换个环境还得重新记。下一篇讲 Docker 常用命令速查手册——把所有日常操作用一张表整理清楚,从 Hello World 到容器全生命周期管理,用到就查,查了就敲。

更多推荐