Docker Compose指南

skillgohub.com 中文指南 | 中文版

Docker Compose指南

Docker Compose 是那种能让周围一切都变轻松一点的习惯。无论你是完全的新手,还是想打磨现有做法的人,理解基础都是走向熟练的第一步。这份完整指南会带你从基础概念一路走到专业人士每天都在用的进阶策略。但更重要的是,它会指出那些真正会在生产环境咬你一口的坑,而不是只讲喜闻乐见的 happy path。

本地跑得飞起的 Compose 文件,一上生产就崩

每个团队都会撞上同一堵墙:在笔记本上开发了六个月都安然无恙的 Compose 文件,一放到全新的生产主机上就开始出问题。原因不是 Docker——而是 Compose 的默认值替你藏了一千个假设:镜像标签和 digest、卷的权限、主机网络、健康检查,以及 buildimage 之间那些说不出道不明的区别。Docker Compose 确实是用最少的力气立起一套多服务栈的方式,也确实是——如果你只学 happy path——最容易搭出“可复现灾难”的方式之一。这篇指南讲的是真实的完整流程:从第一个能跑的栈,到能挺过重启、重开机、同事的全新机器的那种,并在真正要命的地方把坑指出来。

Docker Compose Guide - featured image

你的第一个 Compose 文件:先定结构,再谈容器

Compose 文件把服务声明成 YAML 键,第一个决定是这些服务是拉取镜像还是在本地构建。image: postgres:16 拉一个公共镜像;build: . 读取当前目录的 Dockerfile。要有意识地混着用:基础设施(数据库、缓存、消息队列)用已固定版本、官方的镜像,只有你自己的应用代码才用 build。用 digest 固定(postgres@sha256:...)能保证可复现,而一个裸的 postgres:latest 就是一颗等上游一改就悄悄崩掉你整套栈的定时炸弹。

Docker Compose Guide comparison and review

每个服务都要有个名字,而这个名字就是其他服务在网络上访问它的方式。连接串里要用 Compose 的服务名,而不是 localhost 或某个 IP。这是从裸 Docker 转过来的人最不习惯的一跳:Compose 会按服务名自动建一张内部网络和 DNS。隐式定义 compose 版本(新版本本来就忽略 version 键),先只写最必需的服务键——image/buildportsenvironment——然后随着栈变大再加 volumesdepends_onhealthcheck

卷:你的数据存在哪,以及它怎么挺过一次重建

容器默认是临时的;写在容器文件系统里的东西,容器一重建就没了。这正是数据库和上传目录需要卷的原因。具名卷(volumes: - dbdata:/var/lib/postgresql)由 Docker 管理,能挺过容器重建;而绑定挂载(- ./data:/app/data)把宿主机目录映射进容器,适合开发热重载,但生产环境里如果你宿主路径不稳定,它就是陷阱。按意图选类型:持久化的应用数据用具名卷,迭代中的代码或注入配置用绑定挂载。

Docker Compose Guide step by step guide

卷的权限问题是坑最多的人烧手的地方。不少官方镜像(尤其 Postgres 和 MySQL)以非 root 用户运行,如果挂载目录属于另一个 UID,入口脚本就会抛一句云里雾里的 “permission denied”。解法是匹配 UID——要么在服务里设 user,要么在宿主机 chown 绑定目录,要么用镜像专有的环境变量设置数据目录属主。如果数据库容器第一次跑就立刻崩,先查卷属主再怀疑别的;新环境失败里它的占比高得惊人。

网络与端口:那些不能想当然的默认值

Compose 会给每个项目建一张专属网络、按名给每个服务提供 DNS。只在确实需要外部访问的地方把端口映射到宿主机——Web 服务器或 API(ports: - "8080:80"),但数据库这种只有你应用才该碰的东西绝对不要。生产环境把 Postgres 暴露到 5432,就是亲手给数据库递刀。网络内部的通信用的是服务名和容器内部端口,而不是发布端口。这点很微妙:在内部网络里,你的应用连的是 db:5432,你发布没发布 5432 到宿主机,跟这条路毫无关系。

Docker Compose Guide cost and pricing analysis

当你需要两个 Compose 项目互通时,把它们放到一张共享的外部网络上:networks: - name: shared-net external: true。这在把应用和它依赖拆成两套栈时很常见。还得分清 ports(宿主机映射,有 host:container 简写、容易冲突的 random 选项)和 expose(仅网络可见、不发布)的区别。过度发布端口在共享环境里既是安全问题也是冲突麻烦,只发布可达性真正要求的最小集合。

环境变量、密钥与那个 .env 陷阱

Compose 以两种被混淆的方式读环境变量。.env 文件(躺在 compose 文件旁边)是给 Compose 自己做插值用的——YAML 里的 $VERSION 会去 .env 里解析。environment: 块则把变量放进容器内部。两者都重要,混了就会犯经典的“容器里看不到我的变量”错误。对开发和生产不同的值,优先用插值加按环境的 .env,并把密钥材料排除在 compose 文件和它的提交历史之外。

Docker Compose Guide tools and features overview

对密钥,别把密码写进 environment: 或 compose YAML。用 secrets 机制,或者更常见的真实做法:引用作为卷挂载进来、运行时可创建的密钥文件。把 .env 和任何密钥文件写进 .gitignore——把数据库密码提交进仓库,是每个团队都会干过恰好一次的事。还要明确一点:.env 是在 docker compose up 那一刻求值的,不是每次容器启动。改了 .env 必须重建容器才生效。有点烦,但它能防住拖垮无数调试会话的“我改了但什么都没变”。

用 depends_on 和健康检查控制启动顺序

depends_on 只保证“启动顺序”,不是“就绪”。带 depends_on: - db 的服务会在 db 容器启动后就进入启动流程,但 db 可能还没开始接连接——这就是经典的“connection refused、重试就好了”竞态。现代解法是给依赖配置 healthcheck,再用 depends_on: condition: service_healthy。给依赖定义健康检查(官方镜像通常自带,比如 Postgres 的 pg_isready、Redis 的 redis-cli ping),并让应用等待依赖“健康”而不是“启动”。

给你在乎的每个服务都加健康检查,因为 Compose 和编排平台都用它来判断就绪、重启和负载均衡。健康检查要有 tuned 到你的服务的 interval、timeout 和 retries:太激进,慢启动直接被杀死;太宽松,宕住的服务默默跑着。健康状态会显示在 docker compose ps 和容器检查里,把“它起来了没”从一个问题变成可观测的事实。配上重启策略(服务器上 restart: unless-stopped 是稳妥默认),健康检查才是让 Compose 栈像托管服务、而不是一堆脆皮容器的关键。

扩缩容、资源限制,以及 Compose 管什么、让出什么

Compose 能用 docker compose up --scale web=3 横向扩容某个服务,但前提是这个服务无状态、能通过负载均衡访问。开箱是没有负载均衡器的,所以扩容后的 web 服务各自暴露一个宿主端口——你需要在前面架一个反向代理(NGINX、Traefik、Caddy),或者上带编排的平台。Compose 也能用 deploy.resources.limits 和 memswap 给每个服务设 CPU 和内存上限,这在共享宿主机上很关键,能阻止一个贪吃的容器把别人饿死;否则一个内存泄漏的服务能把整台机器拖垮。

要知道天花板在哪:Compose 是单主机编排。当你要多节点调度、自动扩缩、滚动发布和集群级的自愈时,那是 Kubernetes 的地盘。很多团队在服务超出单机后,会把运行良好的 Compose 栈迁移成 Kubernetes 清单——services、networks、volumes、healthchecks 这套心智模型迁移得很干净,这正是一开始就把 Compose 基础做对的重要原因。

多服务栈的几种 Compose 方案对比

产品 / 工具核心能力价格(人民币参考)
Docker Compose单一 YAML、本地快速起栈、内置于 Docker、健康检查、扩缩容随 Docker Desktop/Engine 免费
Podman Compose无 root 容器、systemd 集成、兼容 Compose开源免费
Rancher Desktop带 Kubernetes(k3s)和 Linux 虚拟机、支持 Compose 的桌面运行环境开源免费
Kubernetes(+ helm/kustomize)多节点编排、自动扩缩、滚动更新、自愈平台成本不一(国内托管如阿里云 ACK 按集群与资源计费,也可自建免费)
PortainerDocker/Compose 栈的图形界面、应用模板、访问控制社区版免费;Business 约 420 元/用户/月起

Compose 是单主机、少量服务场景的合理默认;上面每个替代品都是在不同层用一点点简单性换一个特性。按“今天需要多少台主机和多少编排”来选,而不是按“明年可能要用什么”来选。

用 profile 与覆盖文件,让一个 Compose 工程复用于多个环境

健壮的 Compose 配置把配置和环境分开。用基础的 docker-compose.yml 放所有环境共有的服务,再用按环境的覆盖文件(开发环境的 docker-compose.override.yml 会自动加载;docker-compose.prod.yml-f 显式指定)。覆盖会合并,所以你的 dev 文件能加热重载卷,prod 文件能固定 digest、关掉调试服务,全部来自一个事实源。这比维护三份漂移失控的近似 compose 文件强得多。

profile 更进一步:把可选服务(指标导出器、测试数据库、开发邮件捕获器)标上 profiles: - debug,它们只在传入 --profile debug 时才启动。这让默认的 docker compose up 保持精简,全套工具链又一键可得。栈超过几个服务后,要定义清晰的服务边界、记录有哪些 profile 各自提供什么;profile 泛滥是下一个在 happy path 蜜月期后咬你的东西。好的 profile 和覆盖,是让“本地开发栈”不变成一堆不可维护 YAML 的 monolith 的关键。

排障 Compose:能帮你省一小时的几条命令

栈出问题时,按升级顺序做诊断。docker compose config 校验并打印应用了所有插值和覆盖后的完整解析文件——能立刻暴露环境变量错误,连容器都不用碰。docker compose ps 显示容器状态和健康;docker compose logs --tail=100 <service> 给出出错服务的输出。容器起不来,用 docker compose exec <service> sh 钻进去检查,用 docker inspect <container> 看真实的运行配置、健康状态和退出码。

最常见的坑都有已知特征。“Port already in use” 意味着有个陈旧容器还占着宿主端口——docker compose down 清掉它。容器立刻退出通常是指入口失败:先读日志头几行,如果是卷权限错误就修属主。启动时的间歇性 “connection refused” 指向 depends_on 里少了 condition: service_healthy。而当其他都说不通时,docker compose down -v(注意它会删具名卷!)再 up 能给你一个干净起点——但只有确定数据可以丢时才用 -v

从能跑的 Compose 栈到可维护的基础设施

“能惊艳的栈”和“能活下来的栈”之间的差别,是文档和纪律。在 compose 文件旁放一个 README,说明每个服务干什么、有哪些 profile 和覆盖、卷怎么布局、每个环境的起来命令是什么。固定镜像、给每个服务加健康检查、设好重启策略。密钥不进仓库,卷属主写清楚,只发布需要的端口。这是不起眼的活儿,但它决定了“新同事十分钟就能拉起”和“只有作者能跑”的分水岭。

每次改变服务依赖关系时都重审一下 Compose 文件——加个缓存或队列意味着新的健康检查和依赖链,而不只是加一个服务块。记住 Compose 在栈里的位置:它是你的单主机编排层,它教的这些概念——services、networks、volumes、health、配置走环境——正是你留在 Compose 或迁到 Kubernetes 都需要的词汇。内化了这些基础的团队,它们的容器栈不再每周五制造事故,而是变成平台上平淡、可靠的一部分。

常见问题

我的数据库容器报 “Permission denied” 起不来,到底哪坏了?

几乎总是卷属主问题。容器里的进程以非 root 用户运行,但宿主机目录或具名卷属于另一个 UID(通常 root)。修法是匹配镜像期望的 UID:在宿主机 chown 绑定目录、设镜像的数据目录环境变量(比如某些镜像的 PUID/PGID),或覆盖服务的 user。查一下 docker logs <db> 里的属主线索,再排查其他东西。

Compose 里的 .env 和 environment: 块有什么区别?

.env 是给 Compose 自己在 YAML 插值时读的($VERSION 从它解析),永远不会进容器。environment: 设置的是容器内部存在的变量。如果应用看不到某个变量,那它摆错了地方。把 .env 加入 git ignore、绝不提交;密钥能不放 YAML 和容器环境就尽量不放。

数据库容器明明在跑,为什么我的应用还报 “connection refused”?

因为 depends_on 只保证启动顺序,不保证就绪——你的应用连接时数据库可能还在初始化。给数据库加 healthcheck(比如 pg_isready),并在应用的 depends_on 上用 condition: service_healthy。这样应用等到的是依赖“就绪”,而不是只是“启动”。

什么时候该用 Compose,什么时候该上 Kubernetes?

Compose 适合单主机、少量服务、快速迭代——本地开发和小的部署是它的合理默认。Kubernetes 适合多节点调度、自动扩缩、滚动发布和集群级自愈。如果你从不打算超出单机,Compose 就够了;如果确定要跨节点扩展,早点投资 Kubernetes。service/network/volume/healthcheck 这些概念是直接迁移的。

延伸阅读

想系统建立容器与运维的另一半知识,本站英文站的 Docker 2026 和新手向的 2026 入门指南 是很好的同伴阅读。

把 Compose 基础做扎实后要迈向集群,可衔接本站中文站的 Python 自动化脚本AI 办公自动化技巧,把日常运维的小流程也自动化起来。

📌 Pinterest 🐦 Twitter 📘 Facebook