这是「把自己部署到互联网上」系列的第 20 篇。
前两篇我围绕部署方案做了选择:生产环境到底该用 Conda 还是 Docker,以及为什么最终放弃 Conda 部署。到这里,方向已经确定:这台服务器后面会以 Docker 作为主要部署方式。
但在真正写 Dockerfile、compose.yml、启动容器之前,需要先把两个概念讲清楚:Docker 和 Docker Compose 到底分别解决什么问题?
很多教程会写:安装 Docker,写 Compose 文件,然后执行:
docker compose up -d
照着做可能能跑起来,但如果不理解它们的分工,后面排查问题会很困难:
- 镜像是谁构建的
- 容器是谁启动的
- 多个服务怎么通信
- 端口为什么映射到宿主机
- 数据库数据为什么要挂载卷
- 修改配置后为什么要重新创建容器
先说结论:Docker 解决的是单个服务如何被打包、运行和隔离;Docker Compose 解决的是一组相关服务如何一起启动、连接和管理。
我的个人博客后面包含博客应用、数据库、Redis、Nginx、后台任务,那么单独看每个服务都可以是一个容器。但真正部署时,我需要它们一起运行,并且彼此能找到对方。这时就需要 Docker Compose。
先看 Docker。
如果不用 Docker,在服务器上部署一个 Python 项目,通常要安装 Python、安装系统依赖、创建虚拟环境或 Conda 环境、安装项目依赖、配置环境变量、启动项目。换一台服务器,这些步骤还要重新做一遍。
Docker 的思路是把应用运行需要的环境尽量写进镜像里。项目用哪个基础镜像,安装哪些依赖,复制哪些代码,执行什么启动命令,都可以写进 Dockerfile。然后构建镜像,再从镜像启动容器。
优点
这样做有几个好处:
- 环境更容易复制,不完全依赖服务器上手工安装过什么
- 隔离更清楚,不同项目的依赖不会直接混在系统环境里
- 启动方式更统一,容器有自己的启动命令、端口、环境变量和日志
- 迁移更方便,只要
Dockerfile、配置和数据准备好,就更容易重新跑起来
所以 Docker 解决的核心问题是:如何把一个服务和它的运行环境封装起来。
理解 Docker,要分清镜像和容器的关系:
- 镜像是模板,包含运行环境、依赖、代码和启动方式
- 容器是从镜像启动出来的运行实例。一个镜像可以启动多个容器
可以简单理解为:
Dockerfile -> 构建镜像 -> 启动容器
Dockerfile 是构建说明,镜像是构建结果,容器是运行中的服务。这个关系搞清楚后,很多问题会更容易定位。比如:
- 修改了代码但容器没变化,可能是镜像没重新构建
- 修改了启动命令但行为没变,可能是容器没有重新创建
- 删除容器后数据还在,可能是数据放在卷里
- 删除容器后数据也没了,可能是没有正确做持久化
再看 Docker Compose。
Docker 可以用 docker run 启动单个容器,但真实项目通常不是一个容器就能运行。博客项目可能至少有应用和数据库,如果再加 Nginx、Redis、后台任务,就会有更多服务。
如果每个服务都手动 docker run,命令会很长,也很难维护。你要记住容器名、镜像、端口映射、环境变量、数据卷、网络、启动顺序和服务连接方式。
作用
Docker Compose 的作用,就是把一组服务写进配置文件。常见文件名是 compose.yml 或 docker-compose.yml。在这个文件里,可以描述项目需要哪些服务,每个服务用什么镜像,端口怎么映射,环境变量是什么,数据卷怎么挂载,网络怎么创建。
常用命令也更统一:
docker compose up -d
docker compose down
docker compose logs
Compose 的价值是把多服务项目的启动方式文件化。
比如我的个人博客涉及 Web 应用、数据库、Nginx 入口、环境变量、数据持久化和后续对象存储配置,用 docker-compose.yml 就能把结构写清楚。例如:
services:
app:
build: .
env_file:
- .env
depends_on:
- db
db:
image: mysql:8
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
上面的示意结构。说明的是:项目有哪些服务,哪些数据需要持久化,应用依赖哪个数据库,环境变量从哪里来,都可以在一个文件里看到。需要注意,depends_on 主要处理启动顺序,不等于保证数据库已经完全可用,真正生产部署还要结合健康检查或应用重试机制。
Docker 和 Docker Compose 区别
| 对比项 | Docker | Docker Compose |
|---|---|---|
| 主要解决 | 单个容器的构建和运行 | 多个容器服务的编排和管理 |
| 常见文件 | Dockerfile | compose.yml / docker-compose.yml |
| 常见命令 | docker build、docker run、docker ps | docker compose up、down、logs |
| 关注对象 | 镜像、容器、网络、卷 | 服务组、环境变量、网络、卷、依赖关系 |
| 适合场景 | 单个服务运行 | 一个项目包含多个服务 |
| 配置方式 | Dockerfile 或命令参数 | YAML 配置文件 |
一句话总结:Docker 管容器本身,Docker Compose 管一组容器怎么一起工作。
Docker Compose 里还有几个概念必须提前理解。
第一个是服务名通信
如果应用和数据库在同一个 Compose 项目、同一个网络里,应用可以通过服务名访问数据库。比如数据库服务叫 db,应用可以访问 db:3306,不需要使用服务器公网 IP。
这点很重要,意味着数据库不需要暴露到公网。外部用户访问项目时,先访问 Nginx 或应用入口;应用和数据库之间通过内部网络通信。这也和前面讲的数据库端口安全一致:不要为了让应用连数据库,就把数据库端口开放给全网。
第二个是端口 ports
例如:
ports:
- "8080:8000"
这表示把容器里的 8000 端口映射到宿主机的 8080 端口。如果宿主机安全组和防火墙也放行,外部就可能访问到这个端口。因此 ports 需要谨慎使用,不是所有服务都应该写。数据库服务尤其要谨慎。
Docker Compose 里还有 expose,它不会把端口发布到宿主机,更多是声明容器内部暴露端口。实际使用过程中,先判断下:这个服务是否需要被宿主机或公网访问?如果不需要,就不要随便映射到宿主机。我的服务器架构设计是有一个全局Nginx容器,让其成为统一入口,公网访问尽量集中在 80 和 443,内部服务通过 Docker 网络访问。
第三个是 volumes
容器本身可以删除和重建,但数据库数据、上传文件不能随便丢。所以需要持久化卷:
volumes:
- db_data:/var/lib/mysql
这表示把数据库数据放到持久化卷里。容器删除后,只要数据卷还在,数据就不会跟着容器一起消失。后面部署博客时,我会重点讲下数据库数据、上传文件、配置文件、日志和备份目录分别放在哪里。
第四个是环境变量
个人博客部署会涉及数据库地址、数据库用户、密码、Secret Key、对象存储配置、域名配置、调试模式等。这些不适合写死在代码里。docker-compose.yml 可以通过 env_file 或 environment 注入:
env_file:
- .env
但 .env 文件也要管理好:不要提交到公开仓库,不要多个项目混用同一套密钥。
小结
最后要总结下:Docker Compose 不是 Kubernetes。它不适合管理复杂的大规模集群,但对一台个人服务器、几个个人项目来说足够实用。它比手工 docker run 清晰,又比复杂编排系统轻量,硬上 Kubernetes 意义不大。
理解 Docker 和 Docker Compose 后,后面的部署思路会更清楚。尽量让每个项目都有明确目录,包含:
- 应用代码
- Dockerfile
- `docker-compose.yml``
`.env环境变量文件- 数据卷说明
- 运行日志和配置说明
项目内部服务用 Docker Compose 管理,外部入口由全局 Nginx 容器统一处理。
这也是我从 Conda 转向 Docker 的核心原因之一:不是追求新工具,而是让部署结构更清楚、更容易维护。
下一篇会进入 Nginx 的选择题:Nginx 到底应该装在系统里,还是放进 Docker 容器?当 Docker 方案确定后,外部流量入口怎么设计就是下一个关键问题。
本文由 楸木 原创,转载请注明出处。