Docker 和 Docker Compose 到底分别解决什么问题?

预计阅读时间:12 分钟

这是「把自己部署到互联网上」系列的第 20 篇。

前两篇我围绕部署方案做了选择:生产环境到底该用 Conda 还是 Docker,以及为什么最终放弃 Conda 部署。到这里,方向已经确定:这台服务器后面会以 Docker 作为主要部署方式

但在真正写 Dockerfile、compose.yml、启动容器之前,需要先把两个概念讲清楚:Docker 和 Docker Compose 到底分别解决什么问题

很多教程会写:安装 Docker,写 Compose 文件,然后执行:

docker compose up -d

照着做可能能跑起来,但如果不理解它们的分工,后面排查问题会很困难:

  • 镜像是谁构建的
  • 容器是谁启动的
  • 多个服务怎么通信
  • 端口为什么映射到宿主机
  • 数据库数据为什么要挂载卷
  • 修改配置后为什么要重新创建容器

先说结论:Docker 解决的是单个服务如何被打包、运行和隔离;Docker Compose 解决的是一组相关服务如何一起启动、连接和管理

我的个人博客后面包含博客应用、数据库、RedisNginx、后台任务,那么单独看每个服务都可以是一个容器。但真正部署时,我需要它们一起运行,并且彼此能找到对方。这时就需要 Docker Compose。

先看 Docker。

如果不用 Docker,在服务器上部署一个 Python 项目,通常要安装 Python、安装系统依赖、创建虚拟环境或 Conda 环境、安装项目依赖、配置环境变量、启动项目。换一台服务器,这些步骤还要重新做一遍。

Docker 的思路是把应用运行需要的环境尽量写进镜像里。项目用哪个基础镜像,安装哪些依赖,复制哪些代码,执行什么启动命令,都可以写进 Dockerfile。然后构建镜像,再从镜像启动容器。

优点

这样做有几个好处:

  1. 环境更容易复制,不完全依赖服务器上手工安装过什么
  2. 隔离更清楚,不同项目的依赖不会直接混在系统环境里
  3. 启动方式更统一,容器有自己的启动命令、端口、环境变量和日志
  4. 迁移更方便,只要 Dockerfile、配置和数据准备好,就更容易重新跑起来

所以 Docker 解决的核心问题是:如何把一个服务和它的运行环境封装起来。

理解 Docker,要分清镜像和容器的关系:

  • 镜像是模板,包含运行环境、依赖、代码和启动方式
  • 容器是从镜像启动出来的运行实例。一个镜像可以启动多个容器

可以简单理解为:

Dockerfile -> 构建镜像 -> 启动容器

Dockerfile 是构建说明,镜像是构建结果,容器是运行中的服务。这个关系搞清楚后,很多问题会更容易定位。比如:

  1. 修改了代码但容器没变化,可能是镜像没重新构建
  2. 修改了启动命令但行为没变,可能是容器没有重新创建
  3. 删除容器后数据还在,可能是数据放在卷里
  4. 删除容器后数据也没了,可能是没有正确做持久化

再看 Docker Compose。

Docker 可以用 docker run 启动单个容器,但真实项目通常不是一个容器就能运行。博客项目可能至少有应用和数据库,如果再加 Nginx、Redis、后台任务,就会有更多服务。

如果每个服务都手动 docker run,命令会很长,也很难维护。你要记住容器名、镜像、端口映射、环境变量、数据卷、网络、启动顺序和服务连接方式

作用

Docker Compose 的作用,就是把一组服务写进配置文件。常见文件名是 compose.ymldocker-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_fileenvironment 注入:

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 方案确定后,外部流量入口怎么设计就是下一个关键问题。


本文由 楸木 原创,转载请注明出处。

相关推荐

发现更多