Docker Compose 实战:从安装到多环境编排与排错
2026/9/24 18:24:02 网站建设 项目流程

很多人第一次接触 Docker Compose,都是从“把一串 docker run 命令改写成 YAML 文件”开始的。我最早也是这么干的,以为 Compose 就是一个参数翻译器,把镜像、端口、卷写进文件里就完事。真正在项目里用了半年后才意识到:Compose 的价值不在于省掉几条命令行,而是把“启动一堆容器”这件事,从一个不可复现的手工操作,变成了一份可以提交到 Git、可以评审、可以版本化、到哪台机器都能原样部署的工程资产。特别是最近这一两年,Docker Desktop、WSL2、各种国产化服务器环境来回折腾,踩过的坑多到能写成一本小册子,但反过来也让我把 Compose 的边界、习惯和坑点摸得很透。

这篇文章不是官方文档的复述,而是把我从安装、编排、多环境适配到排错这整条链路里最常用的东西过一遍。适合已经会跑docker run nginx、但想把多个容器真正管理起来的人;也适合那些写好了 Compose 文件却经常在启动时莫名其妙挂掉、想系统排查问题的人。我会把每个关键选择的理由也讲清楚,尽量做到让你看完能直接照着自己的场景落地。

1. 我为什么从裸 docker 命令切到 Compose 编排

先说个真实场景。去年我帮一个朋友搭内部测试环境,需求很简单:一个 Nginx 反代、一个后端 API、一个 MySQL、一个 Redis。用裸 docker 命令的话,光是启动就得敲四五行,每行还拖着-v-p--network--restart一长串参数。如果中间手误把 Redis 的端口映射漏了,容器虽然起来了,反向代理却连不上,你还得花时间复盘刚才到底漏了哪个参数。这种事情发生第三次之后,我就彻底把这种场景迁到了 Compose 上。

1.1 Compose 解决的不是“启动”问题,而是“可复现”问题

docker run不是不能用,它适合一次性调试,比如临时跑个 MySQL 看一眼表结构。可一旦环境里超过两个容器、彼此还要通信,裸命令的短板就很明显了:参数都在 shell 历史里,别人接手时根本不知道这个环境是怎么搭出来的。Compose 的核心价值是用一种声明式的方式,把容器的拓扑结构固定下来。这份 YAML 本身就是文档,新机器上一条docker compose up -d,环境就齐了。

我再举个例子。一条常见的docker run拉满也就几十个参数,但写成 Compose 之后,每个服务的镜像版本、端口映射、依赖关系、健康检查都变成显式字段。代码评审的时候,哪个服务依赖哪个、哪个卷挂在哪个路径,一眼就能看出来。团队协作时这份文件就是唯一事实来源,不会再出现“我本地明明是好的”这种甩锅对话——大家用的都是同一份描述文件。

1.2 什么时候该用 Compose,什么时候不该硬上

Compose 的定位是单机容器编排,它的好用建立在“一台宿主机管理所有相关容器”的前提上。如果你的服务横跨多台机器,需要自动伸缩、节点调度,那应该考虑 Kubernetes 或者 Docker Swarm。不过即使是 K8s 环境,Compose 也不是没有用处——很多项目支持把 Compose 文件转成 Helm Chart 或 K8s Manifest,做开发环境原型验证非常快。

所以我的选型判断很简单:三台机器以内、容器数量在十个以下、没有弹性扩缩容需求,就老老实实用 Compose;再往上走才引入容器编排平台。这也是为什么我在后面讲具体场景时,会把“单机多容器”作为默认前提——不是 Compose 不能做更多,而是它最适合的场景就是这块。

2. 安装与权限:Compose 落地最容易翻车的两个环节

先把环境搞定。这个环节翻车率其实比写 YAML 高得多,尤其是不同系统、不同安装方式混着用的时候。

2.1 Linux 安装:插件版和独立二进制的选择

现在的 Compose 已经分成了两个形态。一个是 Docker 官方的 Compose V2 插件,安装后命令是docker compose(注意中间有空格),它会跟随 Docker Engine 的安装渠道一起分发。另一个是老牌的独立二进制版本,命令是docker-compose(带横杠),一直在独立维护、独立更新。

我在 Ubuntu 上的做法是先安装 Docker Engine,然后顺手安装docker-compose-plugin这个包:

sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

装完验证一下:

docker compose version

输出类似Docker Compose version v2.x.x,说明插件已经可用。如果你跑起来发现提示docker: 'compose' is not a docker command,多半就是插件包没装上。这时候也别慌,用二进制方式装:

sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose

这里有个细节值得说:优先用插件版。因为插件版的命令风格、帮助信息和 Docker CLI 是统一的,升级 Docker 时通常也会一起升级。独立二进制版本虽然也能用,但版本落后时偶尔会出现 YAML 字段解析差异,比如新版的name:顶级字段在老版本上不识别。

CentOS/RHEL 系的安装思路类似,有联网就直接用dnf install docker-compose-plugin;若是离线内网环境,就下载 rpm 包或者拷贝二进制,本质上是同一件事。银河麒麟这类国产化服务器我也装过,底层就是 CentOS 的变体,用 dnf 或者 rpm 方式基本不会碰到障碍。

2.2 Windows 和 macOS:Docker Desktop 的虚拟化依赖

Windows 上安装 Docker Desktop 最典型的报错就是热词里那个virtualization support not detected,还有 Variant 2 的docker desktop failed to start because virtualisation support wasn't detected。这个错误的本质是 Docker Desktop 依赖虚拟化能力,而 Windows 上通常有 Hyper-V、WSL2、BIOS 虚拟化这三层开关,任何一层没开都会导致相同报错。

我的排查顺序是:

  1. 检查 BIOS 里 Intel VT-x 或 AMD-V 是否开启,这一步最容易被忽略,很多品牌的机器默认关着。
  2. 控制面板里启用“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两个功能,然后重启。
  3. 默认 WSL 版本设为 2:wsl --set-default-version 2
  4. 最后才考虑重装 Docker Desktop。

另外还有一个常见的启动报错:failed to connect to the docker api at npipe:////./pipe/docker-desktop-linux。这个一看就是在告诉 Docker CLI 连不上 Desktop 的后端,原因基本都是 Docker Desktop 没完全起来,或者 WSL2 的内核太旧。处理方法是先把 Docker Desktop 退出,再执行wsl --shutdown,然后重新启动 Desktop。这个组合拳能解决大部分“CLI 突然连不上”的问题。

2.3 权限错误:docker 命令需要 sudo 的解法

Linux 装完 Docker,第一次执行docker compose up时经常遇到permission denied while trying to connect to the Docker daemon socket。这不是 Docker 坏了,而是你当前用户不在 docker 用户组里。标准解法:

sudo usermod -aG docker $USER newgrp docker

newgrp docker是让当前会话立即生效,不用重启。如果是在 SSH 会话里,这一步非常关键,很多人执行完 usermod 直接重开终端但没生效,其实是因为 SSH 会话没有重新读组信息,重开一个完整的 SSH 连接就好了。

3. Compose 编排的核心设计:网络、数据卷、依赖与健康检查

安装只是入场券,真正决定 Compose 项目好不好用的,是 YAML 里的编排设计。我见过很多人写 Compose 就是把 docker run 的参数平铺进去,其实 Compose 在网络、生命周期管理上有很多裸命令不好实现的特性,用对了才叫编排。

3.1 自定义网络:服务名即域名

Compose 默认会为每个项目创建一个网络,所有服务都在这个网络里,互相之间可以直接用服务名当主机名访问。比如在web服务里配置连接 Redis,redis这个主机名就指向 Redis 容器的 IP,不用关心 IP 是多少。

但这个默认网络有一个问题:所有服务都在同一个扁平网络里,包括数据库和缓存。安全一点的做法是手动划分网络,让前端、后端、存储各在不同的网段,服务之间按需互联。

networks: frontend: driver: bridge backend: driver: bridge services: nginx: networks: - frontend - backend api: networks: - backend mysql: networks: - backend

这种设计的价值很实际:如果某个服务不幸被攻破,攻击者不能直接横向访问到数据库。真实业务里数据库一般只允许被业务层访问,网络隔离做在 Compose 层是最便宜的手段。

3.2 数据卷:命名卷与 bind mount 的取舍

容器是随时可以销毁重建的,所以持久化数据必须放到宿主机。Compose 里有两种主流方式:命名卷和 bind mount。

命名卷由 Docker 管理,数据放在/var/lib/docker/volumes/下,适合挂数据库的数据文件。它的好处是迁移时一条命令可以把卷导出来,坏处是文件路径不够直观,想看数据得先进容器。

bind mount 是把宿主机的某个目录直接映射进容器,比如/home/user/data:/var/lib/mysql。开发和调试时比较方便,直接能看到文件。生产环境我反而更推荐命名卷,因为你可以用docker compose down -v一键清理所有数据,也可以不带-v保留数据做升级重启。用 bind mount 的话,一旦路径写错,数据可能写到了错误的地方,排查起来更麻烦。

一个需要注意的坑是:数据卷第一次创建时会自动把镜像内对应目录的内容复制进卷里,后续再挂载则不会同步镜像内的变更。所以当你看到“容器里明明有初始化数据,但重启后数据不对”时,先想一想这个卷是不是之前就创建过,里面保留着旧的初始化文件。

3.3 depends_on 只是启动顺序,不是就绪状态

depends_on是最容易被误解的字段。默认情况下它只控制容器启动的先后顺序,不判断依赖的服务到底是否已经准备好接受连接。比如你的应用需要在 MySQL 初始化完成后再启动,但depends_on只能保证 MySQL 容器先启动,不能保证 MySQL 已经接受连接。

解决办法是配合健康检查:

services: mysql: image: mysql:8.0 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 10 api: image: my-api:latest depends_on: mysql: condition: service_healthy

这样 Compose 会等 MySQL 的 healthcheck 通过之后,再启动 api 服务。这里的service_healthy是 Compose V2 才有的完整条件语法,老版本的depends_on只有condition: service_started这种弱语义。

3.4 restart 策略与容器生命周期

Compose 里restart字段控制容器崩溃或宿主机重启后的行为。我常用的三档是:

  • no:不自动重启,适合一次性任务。
  • on-failure:3:非正常退出才重启,最多重试三次,适合可能因偶发网络失败退出、但不想无限重启的任务。
  • unless-stopped:只要不是手动 stop,就会在守护进程启动时拉起来。

生产环境服务我一般用unless-stopped。注意alwaysunless-stopped的细微差别:always会确保容器在 Docker 重启后自动启动,即使你之前手动 stop 过也会被拉起来;unless-stopped尊重手动 stop 的状态。这个差异在维护机器时很有用,有时候临时停容器排查问题,不希望 Docker 一重启就又把容器拉起来。

3.5 一个综合示例:Nginx + API + MySQL + Redis

把上面的设计全部串起来,一个典型的后端项目 Compose 文件长这样:

version: "3.8" networks: frontend: driver: bridge backend: driver: bridge volumes: mysql-data: redis-data: services: nginx: image: nginx:stable-alpine ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./certbot/conf:/etc/letsencrypt:ro networks: - frontend depends_on: api: condition: service_healthy restart: unless-stopped api: image: myapi:1.2.3 environment: - DB_HOST=mysql - REDIS_HOST=redis networks: - frontend - backend depends_on: mysql: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"] interval: 30s timeout: 5s retries: 3 start_period: 20s restart: unless-stopped mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} volumes: - mysql-data:/var/lib/mysql networks: - backend healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 10 restart: unless-stopped redis: image: redis:7-alpine volumes: - redis-data:/data networks: - backend healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped

这里面有几个容易忽略的细节。start_period给 API 留了启动缓冲,不至于一启动因为还没监听端口就连续被判失败。MySQL 的 healthcheck 我用mysqladmin ping而不是mysql -e "select 1",因为前者更轻量,不需要额外装客户端。所有环境变量都用${VAR}引用,不写死明文密码,这引出了下一节的多环境配置。

4. 一套配置适配多环境:env_file、profiles 与配置复用

Compose 文件如果写死了密码、端口、镜像 tag,换一个环境就得改文件再提交,非常难受。多环境适配的核心手法有三个:环境变量、profiles 按需启动、配置片段复用。这一节把这三个东西一次讲清楚。

4.1 .env 文件与 Compose 变量替换

Compose 默认会在项目目录读取.env文件,把里面的键值对注入到模板变量中。比如:

services: api: image: myapi:${API_TAG:-latest} ports: - "${API_PORT:-8080}:8080"

.env里写着:

API_TAG=1.2.3 API_PORT=8080

执行docker compose up -d时,Compose 会自动读取.env,替换${API_TAG}${API_PORT}。如果某个变量没定义,${VAR:-default}语法会使用默认值。这个能力非常实用,因为.env本身可以放在.gitignore里,敏感信息不会进仓库,而 Compose 文件可以保持干净。

需要强调一下:.env文件只用于 Compose 解析时的变量替换。它不会自动变成容器里的环境变量。如果要在容器内使用变量,得在environment字段里显式引用。

4.2 environment 与 env_file 的区别

environmentenv_file都可以给容器注入环境变量,但语义不同。

env_file是把一个文件里的键值对批量传给容器,适合存密码、密钥之类不便于写进 Compose 文件的内容:

services: api: env_file: - ./env/production.env

environment则直接在 YAML 中声明,好处是类型明确,可以和.env联动:

services: api: environment: - DB_HOST=mysql - DB_USER=${DB_USER}

我常用的组合是:变量替换(比如镜像 tag、端口)放在.env,容器的敏感配置放在env_file管理的独立文件里,而像服务名、依赖关系这种拓扑信息直接写在 Compose 文件里。这样每种信息都有唯一的存放位置,不混乱。

4.3 profiles:把可选的辅助服务关在门外

有些服务不是主链路必需的,比如监控面板、调试工具、定时任务。Composeprofiles允许你给服务打标签,默认启动时不拉起它们,需要时再显式启动:

services: redis-commander: image: ghcr.io/joeferner/redis-commander:latest profiles: - debug ports: - "8081:8081"

启动时只带主服务:docker compose up -d,redis-commander 不会启动。需要调试时再用docker compose --profile debug up -d把它拉起来。这会比注释掉服务再恢复要干净得多。

实际用下来,我把debugmonitoringbackup这类非核心服务都打了 profile 标签,生产环境的默认启动项始终克制,需要什么再临时挂载。

4.4 include 与 extends:配置片段复用

项目多了以后,很多 Compose 文件之间会有公共部分。Compose V2 支持的include可以引入外部的 Compose 文件,适合把公共基础设施(比如 redis、postgres)拆成独立的 compose 文件,业务项目再引用:

include: - path: ./common/redis.yml - path: ./common/mysql.yml services: api: image: myapi:latest depends_on: mysql: condition: service_healthy

extends则适合在同一个文件内部复用公共配置片段:

x-common-env: &common-env environment: TZ: Asia/Shanghai LOG_LEVEL: info services: api: <<: *common-env image: myapi:latest worker: <<: *common-env image: myworker:latest

这个 YAML 锚点技巧可以让多个服务共享一份环境配置,避免复制粘贴导致的不一致。注意extends字段本身也支持引用外部文件中的服务,但我在跨文件场景下更推荐include,语义更清晰。

4.5 关于“compose 传参 bundle”的误区

热搜词里有一个“compose 传参 bundle”,我顺带解释一下,很多人以为 Compose 可以像 Helm 那样传一堆参数进去。实际上 Compose 的变量替换主要靠.env和 shell 环境变量,还有--env-file指定自定义 env 文件。如果你要从命令行临时覆盖某个变量,可以直接在命令前定义:

API_TAG=2.0.0 docker compose up -d

这会让 Compose 优先使用 shell 里的API_TAG,而不是.env里的值。所谓“bundle”在 Docker 生态里通常指docker-compose bundle这个实验性命令,用于生成分布式应用打包格式,但它在 V2 里已经基本不维护了。日常需求用环境变量的覆盖机制就够了,不用纠结 bundle 这个词汇。

5. 高频部署场景的 Compose 实战要点

光讲语法和字段有点干,这一节挑几个热搜词里出现频率很高的具体场景,说说我在实际部署时总结的要点和坑。

5.1 用 Compose 部署 MySQL 主从

MySQL 主从的核心是配置文件的差异,但用 Compose 时要注意:两个实例不能同时占用宿主机的 3306 端口。我的做法是把从库映射到 3307 端口,主库正常用 3306,然后从库配置MASTER_HOST指向主库的服务名。

services: mysql-master: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: appdb command: --server-id=1 --log-bin=mysql-bin --binlog-format=ROW volumes: - master-data:/var/lib/mysql ports: - "3306:3306" mysql-slave: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass command: --server-id=2 --log-bin=mysql-bin --binlog-format=ROW --read-only=1 volumes: - slave-data:/var/lib/mysql ports: - "3307:3306"

接下来要做的是进入从库容器执行CHANGE MASTER TO语句,因为不同版本的 GTID 配置有差异,写死在环境变量里反而容易出问题。一个容易踩的坑是:两个容器如果共享同一个数据卷,主从直接就没法配置了,数据文件互相覆盖,务必用不同的命名卷。

5.2 Redis 主从与哨兵

Redis 主从比 MySQL 简单。主库和从库的镜像相同,只是启动参数不同:

services: redis-master: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redis-master-data:/data ports: - "6379:6379" redis-slave: image: redis:7-alpine command: redis-server --appendonly yes --replicaof redis-master 6379 depends_on: - redis-master volumes: - redis-slave-data:/data

如果还要加哨兵,就需要额外在command里指定配置。哨兵常和主从放在同一个自定义网络里,用服务名互相发现。注意 Redis 从库默认是只读的,业务如果错误地写到了从库会直接报错,这是预期行为。

5.3 用 Compose 快速搭建 Harbor 私有镜像仓库

热搜词里有“使用docker compose快速搭建harbor私有镜像仓库”,这其实是个很好的多服务部署案例。Harbor 官方的安装方式就是基于 Compose 的,下载离线安装包后,里面自带docker-compose.ymlharbor.yml,本质上就是帮你管理了一堆组件。

要点:先准备好harbor.yml,修改 hostname 和 harbor_admin_password,然后运行./install.sh。它内部会调用 Compose 把 harbor-core、harbor-jobservice、registry、nginx、postgresql、redis 这些服务全部拉起来。装完之后,docker compose ps能看到这些容器都在跑。

这里最容易踩的坑是:Harbor 默认使用 80 端口,如果宿主机上已有 Nginx 占用 80,会启动失败。解决方法要么改 harbor.yml 里的端口,要么先把现有 Nginx 停掉。另外,Harbor 的压缩任务和 GC 任务偶尔会因为磁盘空间不足报错,设置 Compose 里的日志轮转和定期清理镜像会很关键。

5.4 GitLab、Jellyfin、OpenKM 这类重量级应用

热搜词里出现docker安装gitlabdocker compose jellyfinopenkm这类应用。它们的共同特点是依赖多、启动慢、配置文件多。用 Compose 部署时,我建议重点关注两个东西:

一个是健康检查。GitLab 启动可能要一两分钟,如果依赖它的 CI 或应用不等待就绪直接连,会疯狂重试。给 GitLab 设置一个合理的start_periodinterval,能省很多事。另一个是数据卷。这些应用的状态都保存在文件系统里,我坚持把配置目录、数据目录、日志目录分别用命名卷隔离开。这样升级镜像时只要保留三个卷,应用状态完全不丢。

Jellyfin 这类流媒体服务,要注意显卡和硬件加速设备的映射。比如:

services: jellyfin: image: jellyfin/jellyfin:latest volumes: - config:/config - media:/media devices: - /dev/dri:/dev/dri group_add: - "44"

没有/dev/dri的设备节点就千万别加 devices,否则容器会起不来。这类容器的部署思路,其实比具体的镜像配置更有复用价值——先分析清楚应用的持久化目录、监听端口和硬件依赖,再写 Compose,一步到位。

6. 我踩过的 Compose 排错记录:从网络不通到启动失败

写 Compose 的过程就是不断排错的过程。这一节把热搜词里几个高频报错,按照我实际排查的顺序完整捋一遍。排查思路比答案更重要,换个场景你也能复用。

6.1 failed to connect to the docker api at npipe:////./pipe/docker-desktop-linux

这条报错在 Windows 上太典型了。表面意思:Docker CLI 想通过 Windows 管道连接 Docker Desktop 的后端,但连不上。深层原因一般有两个:

一是 Docker Desktop 没有真正启动完成。Windows 通知栏只显示图标,不代表后端已经就绪。我的排查步骤是先右键退出 Docker Desktop,再执行wsl --shutdown,然后重新打开 Docker Desktop,等右下角出现稳定的鲸鱼图标再跑命令。

二是 Docker CLI 配置指向了错误上下文。有时候你之前连过远程 Docker 环境,上下文被切换了。执行docker context ls看看当前带星号的上下文是什么,如果是desktop-linux以外的名字,用docker context use desktop-linux切回来。这个原因挺隐蔽,我碰到过一次,比重启 Docker Desktop 更治本。

6.2 容器启动后立刻退出的排查链路

docker compose up -d后容器退出,是新手问得最多的问题。我的排查顺序是固定的:

第一步,看服务状态:

docker compose ps -a

第二步,看容器日志:

docker compose logs <service-name>

日志能解决 90% 的问题。比如说 MySQL 容器起不来,日志里经常会直接写明datadir is initialized之类的错误,那基本就是挂载了非空目录到/var/lib/mysql,或者卷里数据版本和镜像版本不匹配。

如果日志没有明显错误,但容器仍在退出,再执行:

docker inspect <container-id>

重点看State.ExitCodeState.Error字段。ExitCode 1 通常是应用内部错误,137 是被 SIGKILL 杀掉,一般和内存不足有关,查看宿主机的dmesg有没有 OOM 记录。

排查一定要逐层往下,不要跳过第一步直接看 inspect,那样很容易被应用层的报错误导。

6.3 网络不通:服务名解析失败与端口映射冲突

容器之间的网络问题,最典型的表现是:A 容器里ping B不通,或者curl http://mysql:3306超时。先确认两个容器在同一个自定义网络里:

docker inspect <container-id> | grep -A 10 "Networks"

如果网络不同,要么把服务加到同一个networks字段下,要么通过external: true连接一个已经存在的外部网络。还有一种情况容易忽略:你在宿主机用localhost访问容器端口,但容器之间互相访问时必须用服务名。服务名解析只有在同一个 Docker 网络里才有效,换成localhost必然失败。

端口映射冲突则更直接,报错是port is already allocated。用docker ps看一下哪个容器占用了端口,或者ss -lntp | grep <port>看宿主机进程。常见的冲突是宿主机上本来就跑了 Nginx 占了 80,Compose 里又要映射 80,一启动就挂。

6.4 镜像下载慢与 registry mirror 配置

国内拉镜像慢是常态,尤其是部署 GitLab、Harbor 这种大镜像时,等待时间让人崩溃。我的做法是给 Docker 配置 registry mirror。

/etc/docker/daemon.json里加入:

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ] }

然后重启 Docker:

sudo systemctl daemon-reload sudo systemctl restart docker

这里有个规律:各镜像加速站点经常变动,别把某个镜像站当成永久方案。我的建议是维护一个列表,定期验证哪个可用。另外,自建 Harbor 之后,可以把大量常用基础镜像同步到私有仓库,项目里统一使用私有仓库地址,速度和安全都能兼顾。

7. 一些值得长期坚持的 Compose 使用习惯

最后说几个我在多次踩坑后总结的个人习惯,不一定适合所有团队,但我觉得参考价值很高。

第一,写好的 Compose 文件在上线前先跑一遍校验:

docker compose config

这个命令会把经过变量替换后的最终配置打印出来,还会识别 YAML 语法错误、无效字段。我每次改完 Compose 都会执行一遍,相当于编译检查,能省掉很多启动时才暴露的问题。配合--quiet参数可以只检查不输出:docker compose config --quiet

第二,善用docker compose topdocker compose stats。前者查看每个服务对应宿主机上的进程,后者实时查看容器资源占用。线上性能出问题时,这两个命令能快速定位是哪个容器把 CPU 打满了,比登录容器一个个排查快得多。

第三,升级镜像前先备份数据卷。比如 MySQL 数据量不大时,直接用 Mysqldump 导出;Redis 本身就开了 AOF,直接停容器复制 AOF 文件也可以。如果没有备份就开始docker compose pull && docker compose up -d,镜像升级失败再回滚时,数据文件可能已经被新版本写坏了。我在一次升级 GitLab 时就吃过这个亏,从那以后每次升级前都强制自己做一次备份。

第四,不要让 Compose 项目长期停留在默认的latest标签。镜像 tag 一旦用 latest,就失去了版本可控性,升级不知道升到哪个版本,回滚也不知道回滚到哪。我在生产环境只允许使用明确版本号的 tag,比如mysql:8.0.36,数据库这类核心组件甚至会把小版本也钉死。

最后再分享一个正在用的小技巧:把整个项目的 Compose 文件、.env.example、初始化 SQL、配置文件模板全部放到同一个目录下,提交到 Git。新环境部署时,只需要git clone下来,复制.env.example.env再填上真实值,接着docker compose up -d。整套流程从零到可用不超过十分钟。如果你也想让别人用你的 Compose 项目,这个结构就是最容易被接受的交付物。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询