Docker Compose 实战:从容器编排到高效部署全攻略

发布时间:2026/9/18 18:05:37
Docker Compose 实战:从容器编排到高效部署全攻略 很多人第一次接触 Docker是从docker run开始的。跑个 Nginx、起个 MySQL一条命令搞定确实爽。但等你真正用它部署项目尤其是涉及微服务、多个中间件、前后端一起跑的时候一条条docker run敲下去命令又长又容易漏参数容器之间的网络关系全凭脑子记。这时候你就能体会到Docker Compose 几乎是官方给的最实在的答案。这篇文章我不打算讲那些花里胡哨的概念就围绕 Compose 的日常使用从为什么要用它、配置怎么写、真实项目怎么编排到常见问题怎么排查完整过一遍。适合已经会用docker run、但对 Compose 还停留在“听说过、没用过”阶段的同学也适合那些已经在用但经常踩坑的人看完应该能少走不少弯路。1. 为什么要用 Docker Compose从一段命令说起1.1 手工管理容器的痛点先还原一个真实场景。假设你要部署一个 Web 项目后端是 Spring Boot数据库用 MySQL缓存用 Redis消息队列用 RabbitMQ。不用 Compose 的情况下你得依次执行四条docker run大概长这样docker network create app-net docker run -d --name mysql \ --network app-net \ -e MYSQL_ROOT_PASSWORDroot123 \ -v mysql-data:/var/lib/mysql \ -p 3306:3306 \ mysql:8.0 docker run -d --name redis \ --network app-net \ -p 6379:6379 \ -v redis-data:/data \ redis:7.0 docker run -d --name rabbitmq \ --network app-net \ -p 5672:5672 -p 15672:15672 \ -e RABBITMQ_DEFAULT_USERadmin \ -e RABBITMQ_DEFAULT_PASSadmin123 \ rabbitmq:3-management docker run -d --name app \ --network app-net \ -p 8080:8080 \ -e DB_HOSTmysql \ -e REDIS_HOSTredis \ myapp:latest这套操作下来问题很明显每次都要手动指定--network漏了就默认走 bridge容器之间互相访问不到。容器启动有先后依赖比如应用容器要在数据库就绪之后再启动手工操作只能靠等。参数一多就记不住换台机器部署等于从头再来。停掉再启动得按依赖顺序反向操作麻烦。这些还不是最要命的。最要命的是这堆命令如果不写进文档、不做到脚本里过两周你自己都忘了当初是怎么起的。而把命令写成.sh脚本本质上还是在用代码对抗复杂Compose 是把这件事做到了“配置即声明”的层面。1.2 Compose 的定义与定位Docker Compose 是 Docker 官方的容器编排工具核心功能就是用一个 YAML 文件描述一组相互关联的容器然后用几条命令完成创建、启动、停止、删除这一整套生命周期管理。你可以把它理解成“容器项目的说明书”。以前你启动一个应用需要 10 条docker run现在把这 10 条命令翻译成 10 个 service 写进 YAML以后不管在任何机器上只要装了 Docker 和 Compose一条docker compose up -d就能把整组服务拉起来。1.3 什么时候必须用 Compose我个人的判断标准很简单只要你的应用不止一个容器就应该用 Compose。别再说“我就一个 Redisdocker run 就够了”——没错单容器确实可以不用但你早晚会遇到加一个定时任务容器、加一个日志收集容器的情况。与其那时候重构不如一开始就把它纳入 Compose 管理。Compose 特别适合四类场景本地开发环境前端、后端、数据库、缓存、消息队列一键拉起成员之间环境一致。中小型项目的单机部署生产环境规模没到 K8s 那个量级一台机器上跑一堆容器Compose 是性价比最高的方案。CI/CD 构建阶段在流水线里动态编排依赖容器跑完测试直接销毁。学习与实验环境比如搭一套 Jellyfin 影音服务、搭一套 Harbor 镜像仓库Compose 文件写好随时复用。要明确一点Compose 默认是针对单机 Docker 的编排工具。如果你的应用需要跨多台机器、需要自动伸缩、需要故障自愈那是 Swarm 或 Kubernetes 的领域不在本文讨论范围内。2. Compose 的核心配置语法与概念精讲2.1 三个核心概念service、network、volumeCompose 文件里的顶层结构主要是三个关键词services、networks、volumes。这三个东西对应容器世界的三个基本诉求跑什么、怎么连、数据存哪。services是核心定义要创建的容器。每个 service 的名字就是你容器要执行的“任务名”例如web、db、cache这个名字同时也是 Compose 内部网络中的 DNS 主机名服务之间可以通过它互相访问。这个设计非常关键——services内部定义的web服务可以直接用http://db:3306去连数据库无需关心数据库容器的 IP。networks定义服务之间的网络拓扑。Compose 默认会创建一个网络所有服务都加入这个网络。你也可以自定义多个网络把服务隔离在不同网段比如前端服务在frontend网络数据库只在backend网络内部对外完全隐藏。volumes定义持久化存储。容器是瞬时的删了重建数据就没了卷就是用来解决这个问题的。可以声明命名卷也可以直接写主机路径挂载./data:/var/lib/mysql区别在于命名卷由 Docker 管理路径更干净主机路径则在备份和查看数据时更直观。2.2 docker-compose.yml 文件的常用配置项这一节是重点我挑高频用到的配置项逐个说每个都给出为什么这么配的原因。version早先版本的 Compose 必须要写version: 3.8之类的内容。从新版尤其是 Docker Compose V2开始version已经废弃写了也可以用但没必要。如果你在网上看到老教程带着 version心里有数就行不用照抄。image指定镜像。Compose 会先检查本地有没有没有就去仓库拉取。这是最常用的方式适合使用现成镜像的服务。build指定构建上下文配合 Dockerfile 使用。Compose 会先用 Dockerfile 构建镜像再运行容器。可以只写build: .也可以写成带参数的形式services: app: build: context: . dockerfile: Dockerfile args: JAR_VERSION: 1.0.0如果image和build同时存在Compose 构建出的镜像会打上image指定的名字和标签方便直观看出来镜像归属。container_name指定容器名。默认容器名是“项目名_服务名_序号”这种自动拼接的格式比如myapp_web_1。虽然看着丑但好处是支持一个服务多副本容器名不冲突。如果你指定了固定容器名就没办法对这个服务做水平扩展了。除非有特殊需求比如外部脚本要按固定名字找容器否则不建议写。ports端口映射格式是宿主机端口:容器端口。写的时候注意两点一是宿主机端口不能冲突二是别暴露不必要的端口。比如 MySQL 如果只在 Docker 网络内部被应用访问就没必要映射到宿主机 3306写成services: db: ports: - 3306:3306如果你本机已经装了 MySQL 占了 3306或者你希望外部无法直连数据库就可以不映射端口或者映射到别的宿主机端口ports: - 127.0.0.1:3307:3306这样只有本机能通过 3307 访问外部网络完全不可达。安全性和灵活性都兼顾了。environment环境变量覆盖镜像默认配置。比如 MySQL 镜像需要MYSQL_ROOT_PASSWORDRabbitMQ 镜像需要默认账号密码这些都可以在这里配置。可以用数组写法也可以映射写法environment: MYSQL_ROOT_PASSWORD: root123 TZ: Asia/Shanghaivolumes每个 service 下的 volumes 定义服务的数据卷挂载。可以挂命名卷也可以挂主机路径。实际项目里最常见的组合是数据库数据挂命名卷配置文件挂主机路径方便改日志目录挂主机路径方便看。volumes: - mysql-data:/var/lib/mysql - ./config/my.cnf:/etc/mysql/conf.d/custom.cnf:ro挂载配置类文件时加:ro只读可以防止容器内误改配置文件这是个好习惯。depends_on声明服务依赖控制启动顺序。比如应用服务依赖数据库只有数据库先启动了应用才会开始启动services: app: depends_on: - db - redis但是要注意depends_on只保证启动顺序不保证依赖就绪。数据库容器启动起来不代表 MySQL 已经可以接受连接应用容器可能照样启动失败。想要真正解决“依赖就绪”问题需要用健康检查配合条件启动这部分后面实操部分会说。restart容器退出时的重启策略生产环境几乎必写。常用的有no默认值退出不重启on-failure非正常退出时重启always总是重启包括 Docker 守护进程启动后自动拉起unless-stopped总是重启除非手动 stop我推荐生产环境服务统一用unless-stopped。为什么不是always因为always的含义是即使你手动停掉容器Docker 重启时它也会再次拉起来。有时候你就想停个服务排查问题结果 Docker 一重启服务全回来了挺烦人的。unless-stopped的意思是“除非你明确停了我不拉其他情况都拉”明显更符合直觉。networks给服务指定网络。前面说过默认会有一个网络但你可以自定义比如有些服务要对外暴露有些只内部访问拆开放在两个网络里更安全。healthcheck健康检查配置这是生产环境非常有用的配置。Docker 会定期执行你指定的命令根据结果判断容器是否健康。后面讲依赖管理时会用到。2.3 环境变量与 .env 文件的使用写配置文件最忌讳把敏感信息直接写死在 YAML 里。先不说安全单就“换个环境部署”来说开发环境的密码是root123生产的密码是另一套总不能每次部署都改文件。Compose 支持自动读取同目录下的.env文件把里面的变量填充到 YAML 中。比如.env文件内容MYSQL_ROOT_PASSWORDroot123 TZAsia/Shanghai然后你的docker-compose.yml可以写成services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} TZ: ${TZ}这样 YAML 文件本身不包含敏感信息不同环境通过切换.env文件完成配置变更。.env文件一定要加进.gitignore这已经是团队协作的基本共识了。还有个细节很多人在 Compose 里把环境变量直接写成${VAR}但没意识到如果变量不存在Compose 会把它当成空字符串。如果你希望变量必须存在可以在 YAML 里用${VAR:?error message}的语法强制校验。这个我在生产环境踩过坑容器明明启动了但行为诡异排查半天发现是环境变量没传进去后来就学乖了。3. 真实项目的 Compose 配置实操3.1 完整案例一中间件三件套MySQL Redis RabbitMQ先来一个几乎所有后端项目都会用到的组合。下面是完整配置可以直接抄作业。services: mysql: image: mysql:8.0 container_name: dev-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123} TZ: Asia/Shanghai ports: - 127.0.0.1:3306:3306 command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-time-zone8:00 volumes: - mysql-data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d:ro healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -p${MYSQL_ROOT_PASSWORD:-root123}] interval: 10s timeout: 5s retries: 5 start_period: 30s redis: image: redis:7.0 container_name: dev-redis restart: unless-stopped ports: - 127.0.0.1:6379:6379 command: [redis-server, --appendonly, yes, --requirepass, ${REDIS_PASSWORD:-redis123}] volumes: - redis-data:/data healthcheck: test: [CMD, redis-cli, -a, ${REDIS_PASSWORD:-redis123}, ping] interval: 10s timeout: 5s retries: 5 rabbitmq: image: rabbitmq:3-management container_name: dev-rabbitmq restart: unless-stopped environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-admin123} ports: - 127.0.0.1:5672:5672 - 127.0.0.1:15672:15672 volumes: - rabbitmq-data:/var/lib/rabbitmq healthcheck: test: [CMD, rabbitmq-diagnostics, ping] interval: 10s timeout: 5s retries: 5 volumes: mysql-data: redis-data: rabbitmq-data:几个值得细说的点MySQL 部分command里设置服务端参数--character-set-serverutf8mb4保证数据库默认字符集是 utf8mb4不然建表时不指定字符集中文和 emoji 存储容易出问题。--default-time-zone8:00是时区设置有些业务对时间敏感如果不设置连接串里头还得加serverTimezone容易漏。./mysql/init:/docker-entrypoint-initdb.d:ro这个目录很实用。MySQL 官方镜像在首次初始化数据目录时会自动执行这个目录下的.sql或.sh脚本。也就是说你可以在项目库里维护一个初始化 SQL放测试数据、建业务库第一次启动时自动完成。注意是仅首次初始化时执行如果数据卷已经存在这个目录下的脚本不会再次触发。健康检查这里用的是mysqladmin ping注意密码是通过环境变量引用的。写mysqladmin的时候在容器内要能找到命令MySQL 官方镜像里是有的放心用。Redis 部分很多人嫌麻烦Redis 默认配置直接跑不设密码。如果你把 6379 映射到公网不出一天就会被扫描并被挖矿程序种上 cronjob这个不是危言耸听。给 Redis 加上密码是基本操作。--appendonly yes开启 AOF 持久化防止重启丢数据数据文件放在/data跟卷对应。健康检查这里有个小坑redis-cli -a 密码 ping会输出警告提示密码在命令行中暴露但不影响执行结果。如果想更干净可以加上--no-auth-warning参数。RabbitMQ 部分镜像选rabbitmq:3-management自带 Web 管理界面映射 15672 就是管理后台。别用rabbitmq:3那种纯服务镜像不然你部署完了还得去容器里手动装插件才能开管理界面多此一举。RabbitMQ 的端口映射有个坑如果只映射 5672 不映射 15672调试消息队列时管理后台访问不到排查问题效率极低。所以我一并映射但都绑到127.0.0.1只有本机能访问避免暴露到公网被爆破。3.2 完整案例二Web 应用 数据库组合这个例子贴近微服务项目的常规结构。假设你有一个 Spring Boot 应用需要连接 MySQL 和 Redis。services: app: build: context: ./backend dockerfile: Dockerfile container_name: myapp-backend restart: unless-stopped ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: prod DB_HOST: mysql DB_PORT: 3306 DB_NAME: myapp DB_USERNAME: root DB_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123} REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123} depends_on: mysql: condition: service_healthy redis: condition: service_healthy mysql: # ... 同上一节的 mysql 配置这里有个关键点如果希望应用在数据库就绪后才启动一定要用新版 Compose 支持的depends_on条件写法。老式depends_on只写服务名不写条件相当于“数据库容器启动了我就启动”但容器启动不代表 MySQL 就绪了。加上condition: service_healthy后Compose 会等待依赖服务的健康检查通过后再启动本服务。注意condition: service_healthy这种写法要求 Docker Compose 支持较新的规范Docker Desktop 用户用的基本都是新版Linux 上直接装 docker-compose-plugin 也能用老旧的docker-compose独立版可能不支持。如果你用的版本不支持最稳妥的方案还是等应用容器自身做连接重试。3.3 构建自己的镜像build 指令在 Compose 中的用法上面案例里用了build: ./backend实际项目里 Dockerfile 一般长这样FROM maven:3.8-openjdk-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM openjdk:17-jre-slim WORKDIR /app COPY --frombuilder /build/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]这个 Dockerfile 用了多阶段构建第一阶段在 Maven 镜像里编译打包第二阶段把打好的 jar 拷贝到精简的 JRE 镜像里。好处是最终镜像里没有构建工具体积小、攻击面小。Compose 配置里写build: ./backend之后首次执行docker compose up -d时Compose 会自动执行构建过程。如果你改了代码要重新构建不能直接up -d它不会主动重新构建要加参数docker compose up -d --build或者先手动构建再启动docker compose build docker compose up -d我习惯用up -d --build一步到位。3.4 单一应用也值得 Compose 化以 Jellyfin 为例有人觉得“我就跑一个应用没必要用 Compose”。我拿影音服务 Jellyfin 举例。从热搜词看出来很多人折腾它。用docker run启动 Jellyfin 的命令大概是docker run -d \ --name jellyfin \ --device /dev/dri:/dev/dri \ -p 8096:8096 \ -v /opt/jellyfin/config:/config \ -v /opt/jellyfin/cache:/cache \ -v /media:/media \ --restart unless-stopped \ jellyfin/jellyfin这条命令已经不短了而且假如你在 NAS 上管理它还要配合其他下载器、字幕工具。用 Compose 管理之后最直接的好处是换机器、重装系统、更新版本之后不需要去翻聊天记录找当初那条命令直接docker compose up -d就回来了。另外Jellyfin 这种带硬件转码的应用需要把宿主机的/dev/driIntel GPU 驱动设备映射进容器这个参数很容易漏。写在 Compose 里一次配好以后就不会忘。4. Compose 的常用运维命令与日常操作4.1 核心命令速查到了日常运营阶段你最常用的命令其实就那么十几条。整理成表格方便查阅操作命令说明启动所有服务docker compose up -d后台运行首次会拉取镜像/构建镜像查看运行状态docker compose ps列出所有服务状态及端口映射查看日志docker compose logs -f跟踪日志输出加服务名只看某个服务的日志进入容器docker compose exec 服务名 bash进入正在运行的容器注意是服务名不是容器名停止服务docker compose stop停止但不删除容器数据还在启动已停止的服务docker compose start和 stop 配对使用停止并删除docker compose down删除容器和默认网络删除并清数据docker compose down -v连卷带命名卷一起删数据会丢慎用重新构建镜像docker compose up -d --build修改代码后需要重新构建镜像时使用查看配置docker compose config渲染最终配置排查变量替换问题很有效拉取/更新镜像docker compose pull拉取镜像的最新版本几个容易记混的点第一up和start的区别。up会根据需要创建镜像、创建容器、启动服务可以理解为“整组拉起”start只是启动之前已经创建好的容器不会创建新容器。日常更推荐用up。第二exec和run的区别。exec是进入一个已经在运行的容器执行命令run是一次性启动一个新的容器执行命令多用于临时任务。比如查看 MySQL 版本docker compose exec mysql mysql --version第三down和stop的区别。stop只是停止容器down会把容器和默认网络都删掉。如果使用--volumes参数还会删除卷那个操作不可逆。4.2 更新服务版本的正确姿势项目迭代过程中更新镜像版本是家常便饭。最朴素的流程是修改代码或镜像版本号。执行docker compose build重新构建如果是自己构建的镜像。执行docker compose up -d启动。确认没问题后执行docker image prune清理旧镜像。Compose 在检测到镜像 ID 发生变化时会重新创建容器而不是直接复用旧的。所以更新时不需要手动删除旧容器直接up -d即可。如果镜像 tag 是固定的比如latest你拉取到了新镜像但 Compose 可能还认为本地镜像没变这时候需要手动指定拉取docker compose pull docker compose up -d或者使用--build参数强制重新构建。4.3 依赖管理与启动顺序的深入理解前面说过depends_on只控制启动顺序不控制就绪状态。一个更完善的方案是给所有依赖服务配置健康检查然后在应用层的depends_on里声明condition: service_healthy。完整逻辑是这样的services: app: depends_on: mysql: condition: service_healthy等mysql的 healthcheck 从starting变成healthy后app 才会被拉起。这比单纯靠restart: on-failure碰运气要优雅得多。但是注意如果 Compose 版本不支持condition: service_healthy你写进去会导致解析失败。我在老机器上踩过这个坑执行docker compose up直接报 unknown field。另外一个小细节就算有了健康检查条件应用自身还是应该做好数据库连接重试。因为 Compose 只保证“启动时刻依赖是健康的”不代表之后一直健康。网络抖动、数据库重启、连接池失效这些都是应用侧要自己能扛的问题。把重试机制写进应用代码里才是真正稳妥的做法。5. 常见问题排查与避坑经验5.1 网络与通信问题实际使用中容器之间互相访问不到是最常见的问题。先看一层层的关系。最常见的原因是服务的网络不在同一个。默认情况下 Compose 会为当前项目创建一个网络所有服务都在这个网络里。但如果你手动docker run启动了一个容器并在里面访问 Compose 的容器默认是访问不到的得把这些容器加入同一个外部网络。跨项目通信的场景比如 A 项目的服务要访问 B 项目的 MySQL有两个方案要么把两个 Compose 项目的网络定义为同一个外部网络要么干脆把被依赖的服务抽出来单独放到一个公共的 Compose 项目里。我推荐后一种因为把两个项目的网络搅在一起会让边界变得模糊后期排查归属问题时脑壳疼。第二个常见问题是容器内 localhost 访问不到。很多新手写了 MySQL 容器映射端口 3306 后在应用容器里用localhost:3306去连数据库失败后一头雾水。原因是每个容器有自己的网络命名空间localhost指的是容器自己不是宿主机。在应用容器里要连数据库应该用 MySQL 服务的名字mysql。第三个坑是端口映射冲突。docker compose up时提示bind: address already in use基本就是宿主机端口被占用。用lsof -i :3306查看是谁占用了端口。当然也可能是一台机器上两个 Compose 项目都映射了同一个端口。5.2 数据持久化与备份恢复数据卷的问题非常隐蔽往往等出了问题才发现。最常见的情况是容器启动成功了数据看着也写进去了但容器一删数据全没了。排查方法检查你的 service 里到底有没有volumes配置别把数据写在容器可写层里。数据卷权限问题也很常见。比如跑 MySQL 时挂载主机目录第一次启动会提示chown: changing ownership of /var/lib/mysql: Permission denied。这是因为容器内的 MySQL 进程是以mysql用户运行的但宿主机挂载目录的属主不是它。这类问题有两个解决思路在挂载前手动给目录设置宽松权限chmod -R 777 ./data。或者在 Compose 文件的volumes挂载时指定:Z标签SELinux 环境适用。格式统一说一下对于数据卷备份我的习惯是定期用docker compose exec mysql执行数据库自身的备份命令把备份文件放到挂载目录里再从宿主机取走。比如 MySQLdocker compose exec mysql sh -c exec mysqldump --all-databases -uroot -p$MYSQL_ROOT_PASSWORD backup.sql这是最朴素但最可靠的备份方式。5.3 镜像拉取慢的排查与优化很多人一开始用 Docker最烦的就是镜像拉取半天不动。遇到拉取慢最常见的解决方法是给 Docker 配置镜像加速源。Docker 官方仓库在全球范围访问都不算快国内更是慢得让人崩溃。给 Docker 配置国内可用的公共镜像源是个常规操作。配置文件在/etc/docker/daemon.jsonLinux或者 Docker Desktop 的设置界面Windows/macOS。我的建议是写两三个镜像源做备选配置完重启 Docker{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }配置完执行sudo systemctl restart dockerWindows 用户直接在 Docker Desktop 的 Settings - Docker Engine 里改 JSON 就行。注意镜像加速源只对 Docker Hub 的公共镜像有效对于一些第三方仓库的镜像比如ghcr.io、quay.io还是要看对应的网络访问情况。另一个优化点是尽量指定具体的镜像 tag。比如不要写image: mysql:latest而是精确到mysql:8.0.36。好处不只是避免破坏性变更还在于镜像层可以复用团队内部部署一致性强。5.4 权限问题与安全配置你会在热搜词里看到“docker权限错误怎么解决”这是一个很经典的坑。刚在 Linux 上装完 Docker执行docker ps大概率报错Got permission denied while trying to connect to the Docker daemon socket原因是 Docker 守护进程的 socket 文件属于root用户和docker用户组当前用户不在docker组里。解决方法是把用户加入docker组sudo usermod -aG docker $USER改完一定要重新登录或者执行newgrp docker才会生效。关于安全再提两个必须注意的点。第一个是不要滥用 privileged 模式。网上很多教程为了让容器跑起来顺手直接写privileged: true这等于给了容器所有宿主机权限如果容器被攻破宿主机基本等于裸奔。绝大多数场景都不需要这个参数。第二个是敏感信息不要写在镜像里。以前出现过把数据库密码写死在环境变量里、然后整个镜像推到公开仓库的惨痛案例。正确做法是使用.env文件配合${VAR}引用或者用 Docker Secret 管理Swarm/K8s 环境至少不要把密码直接写死在 YAML 里。5.5 常见报错速查表积累了一些常见报错按症状整理成速查表方便你遇到问题时快速定位。报错信息原因解决思路bind: address already in use端口被占用lsof -i :端口找占用进程改映射端口或杀进程No such service: xxx服务名写错了用docker compose config查看服务名Container xxx is not running容器启动后崩了docker compose logs 服务名看日志Cannot connect to the Docker daemonDocker 服务没启动或权限不足Linux 上systemctl start docker检查是否加入 docker 组service_healthy未被识别Compose 版本过低升级 docker-compose-plugin改用新版语法Permission denied挂载目录权限不对chmod 目录权限或调整容器内用户Get https://registry-1.docker.io/v2/: net/http: TLS handshake timeout网络问题配置镜像加速源等会儿重试failed to solve with frontend dockerfile.v0Dockerfile 语法或上下文问题检查 Dockerfile 路径、构建上下文是否包含需要的文件5.6 Windows 上使用 Compose 的几个注意点搜索热词里有很多 Windows、Docker Desktop 相关内容。如果你的开发环境是 Windows有两个点帮你避坑。Docker Desktop 启动失败。常见报错类似于Docker Desktop failed to start because virtualisation support wasnt detected。这个基本就是 BIOS 里的虚拟化VT-x/AMD-V没开启或者 Windows 的 Hyper-V 功能没启用。进 BIOS 打开虚拟化然后在“启用或关闭 Windows 功能”里勾选“Hyper-V”和“Windows 虚拟机监控程序平台”重启后一般就能解决。注意 Windows 家庭版对 Hyper-V 支持有限很多人最后选择用 WSL 2 后端来跑 Docker这其实是个更轻量的方案。文件挂载的性能问题。Windows 下把项目目录挂载进容器文件 IO 性能会受影响尤其是大规模编译场景。建议在 WSL 2 内部处理项目通过\\wsl$访问文件让 Docker 的挂载路径保持在 WSL 2 文件系统内性能会好很多。6. 从 Compose 到更复杂的编排使用边界与扩展方向Compose 的单机编排能力很强但也有明显边界。理解了边界你才知道什么时候该换工具而不是硬扛。单机限制。Compose 的调度范围是单个 Docker 节点。如果你有多台服务器要统一编排每台的容器状态各自维护跨节点网络也要手动打通这就不是 Compose 的领域了。弹性和自愈。Compose 不提供故障检测和跨节点重启。虽然可以给容器配置restart策略在单机范围内实现“挂了自动拉起”但节点宕机了Compose 也没有办法把容器迁移到另一台机器。如果真到了需要横向扩展、服务发现、滚动更新到多机集群的阶段我会考虑把整套编排切换到 Kubernetes 或者轻量级的 K3s。但这不是说 Compose 就没用了。K8s 的配置本身也很复杂很多中小型项目用 K8s 属于过度设计Compose 的单机简化方案反而更务实。从我个人的经验来说Docker Compose 是整个 Docker 生态里“投入产出比”最高的一个组件。花一个下午把语法过一遍日常开发部署效率能提升一大截。要说我最后悔什么就是当初没有早点把所有项目都纳入 Compose 管理。那些年手敲docker run的日子是真的回不去了。