Docker 基础概念:镜像、容器和仓库之间的关系
应用在开发环境运行正常,部署到测试服务器后却开始报错。这种情况很可能不是代码的问题,而在于环境:JDK 版本不同、依赖没安装等等。
Docker 要解决的正是这个问题。它把应用代码、运行时和依赖文件打包在一起,只要目标机器提供兼容的容器运行环境,应用通常就能以相近的方式运行。
理解 Docker 的工作方式,需要弄清楚三个概念:镜像、容器、镜像仓库。
目录
镜像
镜像是一个只读模板,包含应用运行所需的全部文件。
以一个 Spring Boot 应用为例,镜像中的内容大致是:
text
基础系统文件(Ubuntu / Alpine 等用户空间文件)
├── JDK 17
├── application.jar
└── application.yml
镜像中虽然包含操作系统用户空间的文件,但它不是一套完整的虚拟机操作系统。容器运行时,仍然使用宿主机的内核。
镜像的分层结构
Docker 镜像不是一个不可拆分的大文件,而是由多层文件系统叠加而成。
一个简单的 Dockerfile:
dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY application.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
构建之后,镜像大致是这样的结构:

Dockerfile 中会修改文件系统的指令,比如 RUN、COPY、ADD,通常会生成新的镜像层。CMD、ENTRYPOINT 等指令主要记录镜像的运行配置,不会单独产生文件系统层。
分层的好处是复用和缓存。两个项目如果基于同一个 JDK 17 基础镜像,可以共享这部分层,不用在磁盘上保存两份。重新构建时,前面没有变化的层可以直接使用缓存。
这也是为什么 Dockerfile 的指令顺序会影响构建速度和镜像体积------后面讲 Dockerfile 优化时会展开。
镜像本身不会运行
镜像只是静态文件,类似于安装包和运行环境的组合。Docker 根据镜像创建容器后,应用进程才真正启动。
容器
容器是镜像的运行实例。
执行 docker run 并不是"把镜像运行起来"这么简单。Docker 会先基于镜像创建一个容器,再启动容器中的应用进程。每执行一次 docker run,通常都会创建一个新的容器:
bash
# 这会创建三个不同的 nginx 容器
docker run -d nginx
docker run -d nginx
docker run -d nginx
如果容器已经存在但处于停止状态,应当使用 docker start 而不是再次 docker run。
容器的可写层
镜像中的文件是只读的。创建容器时,Docker 会在镜像层上方增加一个可写层,容器运行期间产生的修改会记录在这一层中:

同一个镜像可以创建多个容器,它们共享镜像层,但拥有各自独立的运行状态和可写层。修改一个容器中的文件,不会影响其他容器,也不会修改原始镜像。
容器的生命周期与数据
容器停止后,可写层并不会立即消失。docker stop 只是停止容器中的进程,之后仍然可以用 docker start 重新启动,可写层中的数据通常还在。
真正删除容器时(docker rm),可写层才会一起被删除。
因此,更准确的说法是:容器的可写层与容器生命周期绑定。停止容器不会丢失数据,但删除容器时,其中的数据通常也会被删除。数据库文件、用户上传文件等需要长期保存的数据,应该使用 Docker Volume 或绑定挂载保存到容器之外。
常用命令
容器的创建、停止和删除分别对应下面几条命令:
bash
# 创建并启动
docker run -d \
--name my-app \
-p 8080:8080 \
my-springboot-app
# 查看运行中的容器
docker ps
# 查看所有容器(包括已停止的)
docker ps -a
# 停止
docker stop my-app
# 重新启动
docker start my-app
# 删除
docker rm my-app
其中 -d 表示后台运行,--name 指定容器名称,-p 8080:8080 将宿主机端口映射到容器端口。
镜像仓库
镜像构建完成后,需要交付给测试环境、生产服务器或其他开发人员。更常见的做法是把镜像推送到 Registry。
Registry 是存储和分发镜像的服务,中文习惯上也常称为镜像仓库。Docker Hub 和 Harbor 都属于这类服务。一个 Registry 中可以包含多个镜像 Repository,每个 Repository 又可以通过标签区分版本。
Docker Hub 是 Docker 默认使用的公共 Registry,公司内部项目通常使用私有 Registry。Harbor 是常见的私有方案,提供权限控制、镜像复制、漏洞扫描等功能。
拉取和推送
bash
# 从 Registry 拉取镜像
docker pull nginx:1.25
# 查看本地镜像
docker images
# 推送到 Registry
docker push my-harbor.com/myteam/my-app:1.0
镜像名称
一个完整的镜像名称由几部分组成:
text
仓库地址/命名空间/镜像名称:标签
例如:
text
my-harbor.com/backend/order-service:v1.0
拆开来看:
text
my-harbor.com Registry 地址
backend 命名空间
order-service 镜像名称
v1.0 标签
平时执行 docker pull nginx:1.25,省略了前面的地址和命名空间,完整形式接近 docker.io/library/nginx:1.25。
如果省略标签:
bash
docker pull nginx
Docker 默认使用 latest。但 latest 只是一个普通标签,不能确定它指向哪个版本。生产部署时通常不建议依赖 latest,更稳妥的做法是使用固定版本标签,要求更严格时还可以固定镜像摘要。
容器和虚拟机的区别
容器和虚拟机都能隔离应用,但隔离的方式不同。
虚拟机虚拟的是一套硬件环境,每个虚拟机都运行自己的操作系统内核:

容器隔离的是进程、网络和文件系统等运行环境,多个容器通常共享宿主机内核:

因此容器创建和启动的成本更低,但隔离边界也不同于虚拟机。两者不是简单的替代关系,实际部署中也经常在虚拟机里运行多个容器。
完整部署流程
Docker 的完整部署流程如下:

小结
镜像负责交付环境,容器负责运行应用,Registry 负责保存和分发镜像。日常使用 Docker,基本就是围绕构建、推送、拉取和运行这几步展开。
理解了这三个概念,再去看 docker build、docker pull 和 docker run,就不会把它们当成孤立的命令,而是能看出每条命令处在整个工作流的哪个环节。