Docker 到底是什么?用了才知道和虚拟机差多远
说实话,我第一次听到 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 的三个核心概念
搞清楚这三个词,基本就入门了:
镜像(Image):可以理解成"打包好的系统快照",只读。你可以从 Docker Hub 拉取官方镜像(比如 nginx:latest、python:3.11),也可以自己写 Dockerfile 构建。
容器(Container):镜像运行起来就是容器。同一个镜像可以同时跑多个容器,每个都是独立进程,互不影响。你在容器 A 里搞崩了什么,容器 B 完全无感。
仓库(Registry):存放镜像的地方。Docker Hub 是最大的公共仓库,企业内部一般会搭私有仓库(比如 Harbor)。
镜像是怎么来的?——理解分层结构
Docker 镜像有一个很聪明的设计:分层存储(Layer)。
每一条 Dockerfile 指令都会产生一个新的"层",多个镜像可以共享相同的底层。
这样设计的好处是:如果你有两个 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 容器 |
|---|---|---|
| 隔离方式 | 模拟完整硬件 + 独立 OS | 共享宿主机内核,进程级隔离 |
| 镜像大小 | 通常 5GB~20GB | 通常几十 MB 到几百 MB |
| 启动速度 | 分钟级 | 秒级甚至毫秒级 |
| 资源占用 | 重(每个 VM 独占内存) | 轻(容器共享 OS 开销小) |
| 安全隔离性 | 更强(内核完全隔离) | 稍弱(共享内核,依赖 namespace 隔离) |
| 适合场景 | 跑不同操作系统、强安全隔离 | 微服务、快速部署、CI/CD |
血泪教训: 虚拟机不是被 Docker 淘汰了,而是两者解决不同问题。我见过有人把所有东西都往 Docker 里塞,包括需要 Windows 环境的应用——结果在 Linux 上跑 Docker 当然不行,这种情况还是老实用虚拟机。
容器的完整生命周期
一个容器从创建到销毁,经历的状态是这样的:
平时用得最多的就是 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 三个平台都覆盖,图文步骤一步一步来,感兴趣的可以先关注一下。
有问题欢迎评论区交流。
更多推荐





所有评论(0)