说实话,我第一次听到 Docker 这个词的时候,以为它就是个"更轻量的虚拟机"。后来真正用了才发现,这两个东西的思路根本不是一回事——虚拟机是在模拟一台电脑,而 Docker 是在隔离一个进程。

这篇文章不打算搬官方文档,就聊聊 Docker 到底解决了什么问题、它的核心概念是什么,以及和虚拟机的本质区别在哪。


先聊聊 Docker 出现之前,开发是什么体验

你一定遇到过这种情况:代码在自己电脑上跑得好好的,一提交到测试环境就报错。

测试同学找来问你,你说"我本地没问题啊",然后两个人对着环境变量折腾一下午,最后发现是对方 Python 版本差了一个小版本,或者某个依赖库的版本不一样。

这就是传说中的 “Works on my machine” 问题。说白了,根本原因是:不同机器上的运行环境不一样

考虑一下一个 Python 项目要跑起来,需要对齐多少东西:

Python 3.9.x  ──→  你用 3.9.7,他用 3.9.2,看起来一样,实则有坑
pip 依赖版本   ──→  requirements.txt 有没有锁死版本?
系统库         ──→  libssl 版本、libc 版本,玄学 Bug 的温床
环境变量       ──→  忘了在新机器上配,启动就报错
操作系统       ──→  macOS 开发,Linux 部署,有些行为就是不一样

这些变量交叉组合起来,能制造出数不清的诡异 Bug。而且最恶心的是——往往很难复现,也很难排查到底是哪一层出了问题。

Docker 就是来解决这个问题的。


Docker 的核心思想:把环境和应用一起打包

Docker 的思路很直接:既然环境不同会出问题,那就把整个运行环境和应用一起打包,带着走。

你可以把它想象成这样:

以前发布代码,是把代码扔到服务器上,然后祈祷服务器的环境和你本地一样。
用了 Docker,是把"代码 + 运行时 + 所有依赖"打成一个包,整体扔过去,环境就在包里。

这个"包"就是 Docker 镜像(Image),把镜像跑起来,就是 Docker 容器(Container)


Docker 的三个核心概念

搞清楚这三个词,基本就入门了:

docker run

docker pull

docker push

docker commit

📦 镜像 Image
应用 + 依赖 + 运行时的只读快照

🟢 容器 Container
镜像的运行实例,独立、隔离

🗄️ 仓库 Registry
Docker Hub / 私有仓库

镜像(Image):可以理解成"打包好的系统快照",只读。你可以从 Docker Hub 拉取官方镜像(比如 nginx:latestpython:3.11),也可以自己写 Dockerfile 构建。

容器(Container):镜像运行起来就是容器。同一个镜像可以同时跑多个容器,每个都是独立进程,互不影响。你在容器 A 里搞崩了什么,容器 B 完全无感。

仓库(Registry):存放镜像的地方。Docker Hub 是最大的公共仓库,企业内部一般会搭私有仓库(比如 Harbor)。


镜像是怎么来的?——理解分层结构

Docker 镜像有一个很聪明的设计:分层存储(Layer)

每一条 Dockerfile 指令都会产生一个新的"层",多个镜像可以共享相同的底层。

Layer 1:基础 OS(Ubuntu 22.04)

Layer 2:安装 Python 3.11

Layer 3:安装 pip 依赖

Layer 4:复制项目代码

Layer 5:设置启动命令

这样设计的好处是:如果你有两个 Python 项目,它们的底层系统和 Python 版本一样,这两层只需要存一份,磁盘不会重复占用。

一个典型的 Dockerfile 长这样:

# 第一层:基础镜像
FROM python:3.11-slim

# 第二层:设置工作目录
WORKDIR /app

# 第三层:安装依赖(单独一层,方便缓存)
COPY requirements.txt .
RUN pip install -r requirements.txt

# 第四层:复制项目代码
COPY . .

# 启动命令(不产生新层)
CMD ["python", "app.py"]

注意:依赖安装单独放一层,这是个小技巧。因为 Docker 会缓存每一层,只要 requirements.txt 没变,下次构建直接复用这一层,不用重新 pip install,能省不少时间。


Docker vs 虚拟机:到底差在哪?

这是最常见的问题,直接上架构对比:

Docker 架构

🖥️ 物理硬件

宿主机操作系统(共享内核)

Docker Engine

容器 A
(仅应用+依赖)

容器 B
(仅应用+依赖)

容器 C
(仅应用+依赖)

虚拟机架构

🖥️ 物理硬件

宿主机操作系统

Hypervisor(虚拟层)

完整 Guest OS(几GB)

App A

完整 Guest OS(几GB)

App B

对比项 虚拟机 Docker 容器
隔离方式 模拟完整硬件 + 独立 OS 共享宿主机内核,进程级隔离
镜像大小 通常 5GB~20GB 通常几十 MB 到几百 MB
启动速度 分钟级 秒级甚至毫秒级
资源占用 重(每个 VM 独占内存) 轻(容器共享 OS 开销小)
安全隔离性 更强(内核完全隔离) 稍弱(共享内核,依赖 namespace 隔离)
适合场景 跑不同操作系统、强安全隔离 微服务、快速部署、CI/CD

血泪教训: 虚拟机不是被 Docker 淘汰了,而是两者解决不同问题。我见过有人把所有东西都往 Docker 里塞,包括需要 Windows 环境的应用——结果在 Linux 上跑 Docker 当然不行,这种情况还是老实用虚拟机。


容器的完整生命周期

一个容器从创建到销毁,经历的状态是这样的:

docker create

docker start

docker pause

docker unpause

docker stop / 进程退出

docker start

docker kill

docker rm

docker rm

Created

Running

Paused

Stopped

平时用得最多的就是 docker run(相当于 create + start 合在一起),跑完用 docker stop 停掉,用 docker rm 清理掉。


Docker 能用来做什么?

说几个我自己实际用过觉得"真香"的场景:

① 开发环境标准化

团队新来一个人,以前要配半天环境。现在一条命令:

docker-compose up -d

五分钟跑起来整套开发环境,MySQL、Redis、后端服务全齐活。

② 快速尝鲜新技术

想试试某个数据库或工具,以前要安装、配置、用完再卸载(还不一定卸干净)。现在:

docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=123456 postgres:15

一行命令跑个 PostgreSQL,试完直接 docker rm 删掉,宿主机干干净净。

③ CI/CD 流水线

每次构建都在全新的容器里跑,环境完全一致,不存在"上次构建遗留的脏数据"问题。这是 GitHub Actions、GitLab CI 默认的工作方式。

④ 生产环境部署

把应用打成镜像,测试通过后直接推到生产,镜像是同一个,不存在测试环境和生产环境不一致的问题。


踩坑提醒:Docker 不是银弹

用了一段时间,有几个坑还是要提前说:

坑1:容器是无状态的,数据会丢

容器删掉,里面的数据就没了。如果跑 MySQL、Redis 这类有状态的服务,必须挂载数据卷(Volume),把数据存到宿主机上。

# 正确姿势:挂载数据卷
docker run -v /my/data:/var/lib/mysql mysql:8

# 危险姿势:不挂卷,删容器数据全没
docker run mysql:8

坑2:网络配置比较绕

容器之间通信、宿主机访问容器、容器访问外网……有一套自己的网络模型(bridge、host、overlay 等),刚开始容易懵。建议先把默认的 bridge 网络搞清楚,够用了。

坑3:时区问题

Docker 容器默认是 UTC 时区,在中国用的话,日志时间会差 8 小时。跑容器时记得加:

-e TZ=Asia/Shanghai

或者在 Dockerfile 里设置时区,血泪教训。

坑4:镜像越来越大

Dockerfile 写不好,每次构建都往里塞东西,镜像越来越臃肿。后面我会专门写一篇"如何用多阶段构建把镜像体积砍掉一半"。


写在最后

Docker 解决的核心问题就一个:让运行环境可复现、可移植

理解了这一点,再去看镜像、容器、Compose 这些概念,就会顺很多。它不是"又一个要学的技术",而是"一个让你以后少踩坑的工具"。

下一篇我会写 Docker 的安装教程,Windows / Mac / Linux 三个平台都覆盖,图文步骤一步一步来,感兴趣的可以先关注一下。

有问题欢迎评论区交流。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐