“来一份汉堡套餐”的真正含义
上一次,我们用 Dockerfile 做出了属于自己的“Docker 镜像(Image)”。这就像是一份“永远不会变化的菜谱”,里面写着面包上先放肉排,再铺生菜的顺序。
现在,轮到我们拿着这份菜谱,真正开始做生意,也就是提供服务了。可一旦开始使用 Docker,就会不断冒出一些容易混淆的词:Container、Service、Stack……它们看起来都差不多,那到底有什么区别?
如果这些概念没理顺,之后写部署脚本时你就会陷入迷茫,一边嘀咕“为什么容器启动了,服务却没起来?” 今天,为了把这条家谱彻底理清,我带来了全世界通用的语言:汉堡店比喻。

1. 容器(Container):接单后做出来的汉堡
- 定义:把镜像运行起来、加载到内存里的“实体”。
- 特点:你不能把菜谱纸(镜像)直接递给顾客,而是要给他按菜谱做好的、热乎乎的“汉堡(容器)”。
- 命令:
docker run my-burger
如果我启动了一台服务器实例,那就意味着我运行了“一个容器”。
2. 服务(Service):同一菜单的库存管理
到了午餐时间,顾客一下子涌了进来。按一个个汉堡现做的速度,根本撑不住。于是我们决定增加厨房人手,提前准备好 3 个“芝士汉堡”。这时,我管理的就不再是“一个芝士汉堡”,而是整条“芝士汉堡生产线”,也就是 Service。
- 定义:用同一个镜像做出来的“容器集合”。
- 特点:可以给它设定“这个菜单始终保持 3 份库存(Replicas=3)”这样的“策略”。要是掉了一个汉堡(出错),它会自己再烤一个新的。
- 目的:“横向扩展(Scale-out)”。分散流量,提高稳定性。
3. 栈(Stack):套餐,也就是完整打包
只吃汉堡会噎住。你还需要薯条,DB,也需要可乐,Redis。只有这些都凑齐了,才能算得上是“完整的一餐”,也就是应用程序。
- 定义:把不同的服务(Web、DB、缓存)捆在一起的“最上层分组”。
- 特点:汉堡和可乐要放在同一个托盘上才方便吃(网络连接)。一旦打包成栈,它们之间就会有一个可以互相通信的专用空间(Network)。
问题:光是点单就要喘不过气了
概念上是懂了,可真要运转起来,脑袋就开始疼了。我们要构建的 Web 服务,也就是这个 Stack,通常会需要下面这些组成部分。
- Spring Boot (Burger):主菜 2 份(冗余备份)
- PostgreSQL (Fries):配菜 1 份
- Redis (Coke):饮料 1 杯
如果用最原始的 Docker 命令,docker run,去把这一切跑起来,就像是在柜台前这样点单。
“你好,我要一个‘芝士汉堡(Spring)’,不过肉排请帮我煎到 medium-well,里面别放酸黄瓜,酱多一点。啊,对了,再来一份‘薯条(DB)’,但是不要放盐,番茄酱给我 3 包。最后‘可乐(Redis)’要零度可乐,冰块只放一半。然后请把这 3 样都放在‘同一个托盘(Network)’上。”
# 實際上每次都得敲這種指令
$ docker run -d --name my-web -e PATTY=medium -e PICKLE=no -p 8080:8080 ...
$ docker run -d --name my-db -e SALT=no ...
$ docker run -d --name my-redis -e ICE=half ...
每次部署都要一字不差地背出这段超长订单吗? 如果顺序搞错了,薯条比汉堡更晚出来呢? 顾客,也就是用户,肯定会发火。
救世主:Docker Compose – “老样子来一份!”
于是开发者们想到了一个办法。 “能不能把这种复杂的订单内容,先写在‘菜单,文档’上,然后只要说一句‘给我 1 号套餐’就行?”
于是,“Docker Compose”就诞生了。只要我们在一个叫 docker-compose.yml 的文件里定义好自己的“定制套餐”,Docker 就会自动把订单发给厨房,再按顺序把所有东西整齐地摆上托盘。

[Action] 制作属于自己的套餐
别只停留在口头上,我们来真的:一次性把本地的 Spring 服务器和 DB 一起拉起来。你要做的只是,在项目根目录下创建一个 docker-compose.yml 文件,然后把下面内容贴进去。
version: '3.8'
services:
# 1. 薯條(DB)
my-database:
image: postgres:14
environment:
POSTGRES_PASSWORD: mysecretpassword
ports:
- "5432:5432"
# 2. 起司漢堡(Web)
my-web-server:
image: my-spring-app:latest
ports:
- "8080:8080"
depends_on:
- my-database # "先把薯條炸好才能出漢堡" (確保順序)
environment:
DB_URL: jdbc:postgresql://my-database:5432/mydb
然后在终端里念出那句魔法咒语。
# "來一份 1 號套餐(docker-compose.yml)!"
$ docker-compose up -d
结果: Docker 会读这份菜单文件,先把 DB 启动起来,等 DB 准备好,再去启动 Spring 服务器。这就是 IaC,Infrastructure as Code,“用代码管理基础设施”这个概念的真正起点。以后再复杂的服务器配置,也只需要共享一份“菜单文件”就够了。
结语:Works on My Machine? Works on ANY Machine!
这个系列最开始的那句借口——“在我电脑上明明可以跑……”——到这里已经不再成立了。我们理解了 Linux,也就是 OS;用 Docker,也就是 Container,隔离了环境;再通过 Docker Compose,把复杂服务定义进了一张文件里。
现在,只要我手里有自己写好的 docker-compose.yml,无论是在我的电脑上、组长的电脑上,还是在 AWS 服务器上,我都能在一分钟内摆出同样的一份汉堡套餐。
我们已经不再只是“写代码的人”,而是能够连自己写下的代码将要运行的世界,也就是环境,一起设计出来的“工程师”。至此,[Works on My Machine] 系列结束。
从下一套系列开始,我们将站在这套稳固的基础设施之上,进入 [The Pipeline: CI/CD 自动化] 的世界,把繁琐的部署工作彻底交给机器。


