Docker-Compose 这套命令,我没记错的话,前前后后用了快五年。从最早拿它编排一个 MySQL + Redis + Nginx 的小项目,到后来在生产环境里维护 Prometheus + Grafana 监控全家桶,再到帮同事排查 Chat2DB 这类工具的容器化部署问题,几乎每天都要跟 docker-compose 打交道。很多人觉得 compose 不就是 up 和 down 吗?真上手才会发现,这里面的门道远比想象中多,比如 docker-compose up 怎么限制容器配置,离线环境怎么装 docker-compose,compose 跑起来之后怎么排查网络和服务状态。这篇就把我用过的、踩过的、总结出来的命令细节一次性讲透,适合刚接触容器编排的开发者,也适合已经用了很久但一直靠复制粘贴的老手。
1. 为什么是 docker-compose,而不是一串 docker run
1.1 多容器应用的编排痛点
先聊聊最基础的问题:为什么非要 docker-compose?我自己刚接触容器的时候也这么想过——要起一个 MySQL,docker run一条命令就够了;再起一个 Redis,再来一条;重复几次不就行了?真去做了才发现,痛点在“环境一致性”和“服务关联”上。
比如一个典型的 Web 项目,后端服务依赖数据库,数据库初始化需要挂载数据卷,Redis 需要网络别名才能让后端通过固定主机名访问。你用 docker run 一条条执行,顺序、网络、数据卷全靠人脑记,换一台机器部署,又要重新敲一遍。万一某个服务启动时依赖另一个服务已经就绪,你还得自己写循环等待脚本,这活儿我干过一次就不想干第二遍。
docker-compose 解决的就是这个问题:把服务定义、网络、数据卷、依赖关系全部写进一个docker-compose.yml,然后用一组统一命令来管理整个生命周期。说白了,它就是多容器场景下的“项目说明书 + 遥控器”,一条up全部拉起,一条down全部清理,依赖关系由 compose 在启动时自动处理,不用你操心顺序。
1.2 compose 文件与命令的关系
很多新手容易犯一个误区:把 docker-compose 当成一个独立软件,却忽略它和 Docker Engine 的关系。实际上 docker-compose 本身不做容器运行时的工作,它更像是一个“编排客户端”,把 YAML 文件里定义的服务翻译成 Docker 原生的 API 调用,再由 Docker Engine 真正去创建、启动、销毁容器。
这就解释了为什么你执行docker-compose up,看到的日志格式和docker run很像,但对象却是一整组服务。所以我一直建议,学命令之前先把docker-compose.yml里的三个核心字段搞清楚:services(定义有哪些服务)、networks(定义服务间如何通信)、volumes(定义数据如何持久化)。命令是对这三个维度做增删改查,文件才是灵魂。
另外提醒一句,docker-compose 默认会在当前目录下找docker-compose.yml或docker-compose.yaml,如果你的文件名不一样,比如叫stack.yml,那后面所有命令都得加-f stack.yml,否则它会抱怨找不到配置文件。这个点不复杂,但我在实际工作中见过不少人栽在这里。
2. docker-compose 命令全景详解
2.1 生命周期类命令:up、down、restart、stop、start
生命周期命令是使用频率最高的,先讲up。docker-compose up的作用是创建并启动所有服务,如果镜像不存在会自动拉取,如果有配置变更会重建容器。我第一次用的时候以为它等同于 start,实际上差别很大:start 只是启动已经存在的容器,up 会检查配置并决定是否需要重建。
日常最常用的几个参数我列一下:
docker-compose up -d:后台启动,不加 -d 会前台挂住,日志直接刷屏,适合调试,但不适合日常使用。docker-compose up --build:启动前强制重新构建镜像,改了 Dockerfile 后必须加这个参数,否则它还用缓存里的旧镜像。docker-compose up --no-deps:只启动指定服务,不启动它依赖的服务,排查单个服务问题时很有用。
再说down,它和 up 是反操作,会停止容器并删除网络。注意:默认情况下它不会删除数据卷,所以数据库数据还在。如果你确定要连数据卷一起清掉,用docker-compose down -v,这个 -v 是“连窝端”,执行前一定要确认数据不需要保留,我见过同事手滑把整个测试库给清了。
restart、stop、start这三个就比较直白。restart 重启所有服务或指定服务,stop 优雅停止,start 重新启动已停止的容器,不会重新创建。有个小细节:如果你改了 YAML 文件里的环境变量或端口映射,然后执行docker-compose restart,改动不会生效,必须up -d重建。这个问题坑过很多人,包括我。
2.2 状态与调试类命令:ps、logs、top、exec、run
服务跑起来之后,怎么观察状态?首推docker-compose ps,它会列出当前目录对应项目下的所有容器,显示状态、端口映射、启动命令。加-a可以显示已经停止的容器,排查崩溃原因的时候很有用。
看日志用docker-compose logs,不加参数默认输出所有服务的日志,服务多了会非常乱。我一般这么用:
docker-compose logs -f --tail=100 服务名:追踪某个服务最后 100 行日志。docker-compose logs --no-color:关闭颜色输出,重定向到文件时不会有一堆转义字符,看起来清爽很多。
top命令可以查看容器内运行的进程,和 Linux 上的 top 类似,但作用对象是某个服务,比如docker-compose top web,可以确认容器里到底跑了哪些进程,排查进程异常退出时很管用。
exec和run是最容易混淆的一对。docker-compose exec 服务名 命令是在已经运行的容器里执行命令,适合进去看文件、调试环境变量,比如docker-compose exec db mysql -uroot -p可以直接进数据库。docker-compose run --rm 服务名 命令则是临时启动一个新容器来执行命令,执行完销毁,--rm保证不留垃圾容器。两者的核心区别:exec 用现有的容器,run 新起一个。
2.3 构建与镜像管理类命令:build、pull、push、images
如果你的服务镜像需要本地构建,比如自定义了 Dockerfile,就不能只靠 up 去拉远程镜像了。docker-compose build专门负责构建镜像,构建规则写在 YAML 的build字段里,包括构建上下文、Dockerfile 路径、构建参数。常用参数是--no-cache,当你怀疑构建缓存导致代码没更新时,强制全量重新构建。
docker-compose pull负责拉取镜像,在部署前提前把所有镜像拉下来,避免启动时网络超时。push则是把本地构建的镜像推送到镜像仓库,用于 CI/CD 流水线,单人项目用得少,但团队协作时很关键。
docker-compose images列出项目当前使用的镜像和标签,我习惯在升级版本前后各执行一次,对比镜像版本变化,操作记录一目了然。
2.4 配置与校验类命令:config、version
docker-compose config是我认为被低估最严重的命令。它会读取当前的 YAML 文件,把环境变量替换、默认值补充、多个文件合并后的最终配置完整打印出来。修改配置拿不准时,先跑一下这个命令,看解析结果对不对,比直接 up 然后报错再改要高效得多。
举个例子,YAML 里用了${MYSQL_ROOT_PASSWORD}这样的环境变量占位,如果不确定 .env 文件有没有正确加载,执行docker-compose config就能看到最终字符串是什么,密码有没有变为空值,一目了然。还有--services参数,只列出所有服务名,写脚本遍历服务时很实用。
docker-compose version没啥好说的,查看 compose 版本号,但有个细节:如果你用的是 docker-compose v1,命令是带横杠的独立二进制;如果是 v2 插件,可能要用docker compose(无横杠)。两者命令参数基本兼容,但有些细微差异,遇到报错先确认版本再排查。
3. 实战:用 docker-compose 部署 Prometheus + Grafana 监控站
3.1 准备 compose 文件
理论说再多不如跑一个实际例子。这里我以最经典的 Prometheus + Grafana 监控组合来演示,这套方案我在内网环境里部署过多次,整个流程可以直接抄作业。
先创建目录和 compose 文件:
mkdir -p /opt/monitor/{prometheus,grafana} cd /opt/monitor vim docker-compose.yml在docker-compose.yml里写入下面内容:
version: '3.8' services: prometheus: image: prom/prometheus:v2.45.0 container_name: prometheus ports: - "9090:9090" volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' restart: unless-stopped grafana: image: grafana/grafana:10.1.0 container_name: grafana ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin123 - GF_INSTALL_PLUGINS=grafana-clock-panel volumes: - grafana-data:/var/lib/grafana depends_on: - prometheus restart: unless-stopped volumes: prometheus-data: grafana-data:这里面有几个值得注意的点。depends_on只是控制启动顺序,不保证服务内部真正就绪,也就是说 Grafana 容器起来了不代表它已经能连上 Prometheus,这只是启动顺序的“软依赖”。如果你有初始化等待需求,得自己写脚本或者用健康检查,可以后面再加。
3.2 启动与验证
配置写好后,先做配置校验:
docker-compose config -q-q表示安静模式,配置合法就不输出任何内容,有错会直接报错。没问题后再启动:
cd /opt/monitor docker-compose up -d这里我实测下来最担心的是镜像拉取速度,如果你的网络环境一般,可以先执行docker-compose pull单独拉镜像,或者干脆配置镜像加速器。启动完成后用docker-compose ps查看状态,两个服务都应该是 Up 状态。
然后打开浏览器访问http://服务器IP:3000,默认用户名 admin,密码就是我在环境变量里设置的admin123,登录后选择数据源类型 Prometheus,地址填http://prometheus:9090——注意这里填的是服务名,不是 IP,因为 compose 会在项目内部创建一个默认网络,服务名就是容器之间的 DNS 域名,这个机制省去了自己维护 IP 的麻烦。
3.3 滚动升级与配置热加载
监控服务跑久了,总要升级版本。我的做法是先修改 YAML 文件里的镜像标签,比如从v2.45.0改成v2.47.0,然后执行:
docker-compose up -d --no-deps prometheus--no-deps告诉 compose 只处理 prometheus 这个服务,不要碰 Grafana,避免误重建。实测这个操作很快,新容器起来后旧容器会被自动删除,数据卷没有丢失,历史监控数据都在。
还有一个细节:Prometheus 修改告警规则后不需要重启容器,发送 SIGHUP 信号即可加载新配置:
docker-compose exec prometheus kill -HUP 1PID 为 1 是因为容器内 prometheus 进程就是主进程。这个小技巧能避免不必要的容器重建,长期运行的服务尤其推荐。
4. 进阶用法与生产环境必知项
4.1 docker-compose up 限制容器配置
生产环境最怕的就是资源争抢。你不可能让监控容器和业务容器抢 CPU 抢内存,docker-compose 也支持在 YAML 里直接限制资源。在服务定义下面加deploy字段:
services: prometheus: image: prom/prometheus:v2.45.0 deploy: resources: limits: cpus: '1.5' memory: 2G reservations: cpus: '0.5' memory: 512M有人会问,docker-compose 不是单机编排工具吗,deploy不是 swarm 才有的吗?这里要特别注意:compose v2 已经支持deploy.resources字段,因为 Docker Engine 底层会把解析结果转成容器运行时资源限制参数,所以即便不用 swarm,limits依然生效。如果不放心,可以用docker inspect 容器名查看HostConfig.Memory值是否非零。
内存限制生效后有个坑:如果进程实际占用超过限制,容器会被 OOM Killer 杀掉,重启策略如果是unless-stopped会反复重启,看起来像抖动。我的经验是内存限制一定要给足余量,Prometheus 这类时序数据库对内存很敏感,至少按平常峰值的 1.5 倍来配。
4.2 离线安装 docker-compose
内网环境不能联网,装 docker-compose 是很多运维头疼的事。常规在线安装是直接下 GitHub release 的二进制文件,但内网机器访问不了外网,就必须走离线通道。
我常用的办法是找一台能联网的机器下载对应版本的二进制文件,然后拷贝到内网。这里关键在于版本对应,比如 Docker Engine 是 20.10.x,建议用 docker-compose 2.12.x 以上的版本,兼容性更好。下载命令参考:
wget https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64把文件拷到内网机器后,重命名并赋予执行权限,然后移动到系统路径:
mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose docker-compose versionchmod +x这步漏掉,执行时会报Permission denied,这是我见过最多的离线安装失败原因。另外如果你的内网机器是 ARM 架构,比如飞腾、鲲鹏这类国产芯片,需要下载docker-compose-linux-aarch64版本,x86 的二进制在 ARM 上跑不起来,会报exec format error。
还有一种离线方式是直接打包 Docker 镜像,docker-compose本身不依赖镜像,但这个思路适用于内网部署整套应用:在有网的机器上docker-compose pull拉好镜像,然后docker save打包成 tar 文件,拷到内网用docker load导入,再docker-compose up -d。这套流程我在没有外网的隔离环境里验证过多次,非常稳。
4.3 集成 PostgreSQL 与 Chat2DB 的实战组合
除了监控,数据库容器化也是高频场景。我最近帮团队搭了一套本地开发环境,就是用 docker-compose 跑 PostgreSQL 加 Chat2DB 的容器版。
Chat2DB 是一个数据库客户端工具,支持 AI 辅助 SQL 生成,官方提供了 docker-compose 部署方式。核心的 compose 片段大概是:
services: postgres: image: postgres:15 environment: POSTGRES_USER: dev POSTGRES_PASSWORD: dev123 POSTGRES_DB: myapp ports: - "5432:5432" volumes: - pg-data:/var/lib/postgresql/data chat2db: image: chat2db/chat2db:latest ports: - "10824:10824" depends_on: - postgres这里要提醒的是 PostgreSQL 的数据卷挂载路径,不同大版本有差异。比如 PostgreSQL 14 及以下版本数据目录是/var/lib/postgresql/data,PostgreSQL 15 开始官方镜像改成了/var/lib/postgresql/data下的子版本号目录,如果你直接挂载主机目录,启动时可能会提示权限问题。解决办法是挂到一个空目录,让容器自己初始化,不要先往里面放文件,否则会触发“data directory has wrong ownership”的错误。
5. 常见问题与排查技巧实录
5.1 端口冲突
这是 docker-compose up 最常见的报错之一:Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use。说明宿主机上某个进程已经占用了 3306 端口。排查方式:
sudo ss -lntp | grep 3306找到占用进程后,要么停掉它,要么修改 YAML 文件里的端口映射,把"3306:3306"改成"33061:3306",不影响容器内部端口。我一般更喜欢改宿主机端口,因为数据库容器内部端口变了会导致连接串全部要改,麻烦。
5.2 网络连接不上
两个服务在同一个 compose 项目里,理论上可以通过服务名互通。如果出现连接不上,先检查两者是否在同一个网络里。执行:
docker network ls docker-compose ps再查看服务容器的网络归属:
docker inspect 容器名 | grep -A 20 "Networks"最常见的原因是配置文件里手动指定了network_mode: host,导致容器不再使用 compose 默认网络,服务名无法解析。解决办法是去掉network_mode,改用ports端口映射。
5.3 容器启动顺序与依赖
即使写了depends_on,也不能保证服务完全就绪。比如数据库容器虽然起来了,但 PostgreSQL 还在初始化,此时依赖它的应用去连数据库必然失败。解决方案是在应用启动命令里加入等待脚本,或者使用healthcheck加condition: service_healthy的组合。
>> 提示:compose v2 支持 `depends_on` 的 `condition` 字段,前提是依赖的服务定义了 `healthcheck`。比如 PostgreSQL 的健康检查可以是 `pg_isready` 命令,这样应用容器会在数据库真正可连接后才启动。services: postgres: image: postgres:15 healthcheck: test: ["CMD-SHELL", "pg_isready -U dev"] interval: 5s timeout: 3s retries: 10 app: build: . depends_on: postgres: condition: service_healthy### 5.4 常见错误速查表 我在日常维护中整理了一个高频错误对照表,遇到问题先对照排查,大多数情况能省下大把时间。 | 报错信息 | 原因 | 解决方案 | | --- | --- | --- | | `No such service: xxx` | 服务名写错或文件不对 | 用 `docker-compose config --services` 列出所有服务名 | | `Cannot create container for service` | 容器名冲突 | `docker rm -f` 删除同名的旧容器 | | `Image ... not found` | 镜像不存在或拉取失败 | 先 `docker-compose pull`,检查镜像标签是否打错 | | `version is obsolete` | compose 版本过高 | 删除 `version` 字段或升级 docker-compose | | `Service ... failed to build` | Dockerfile 构建失败 | `docker-compose build --no-cache 服务名` 查看完整日志 | | `volume is in use` | 数据卷被某个容器占用 | 找到占用容器并停止,再 `docker-compose down -v` 清理 | | `Permission denied` | 数据卷权限不足 | 检查挂载目录属主,`chown` 或 `chmod` 调整 | 这张表里的问题,我几乎每个都踩过。尤其是 `version is obsolete`,docker-compose v2 已经可以直接忽略 `version` 字段,写了反而在个别新版本上报提示,删掉就好。 ## 写在最后的经验 这几年用下来,我对 docker-compose 最大的体会是:命令本身不难,难的是理解它背后的对象模型。你不需要背每一个参数,但一定要清楚自己面对的是“一组服务”,up、down、ps、logs 都是在操作这组服务,这和 docker run 操作单个容器的思维模式完全不同。 如果再让我给新手一个建议,那就是遇到问题先从 `docker-compose config` 开始排查,而不是直接 up。配置解析对了,后面百分之八十的问题都能避免。还有一个小技巧,写完 YAML 文件之后,用 vim 打开时注意缩进,YAML 对空格极度敏感,一个错误缩进就能让整个文件白忙活。我在终端里编辑配置时,习惯先 `:set list` 显示空格和制表符,防止混用 Tab 导致解析错误。 最后分享一下我的日常工作流:部署前 `config` 校验,启动时 `up -d`,观察状态用 `ps`,出问题看日志 `logs --tail`,进容器排查用 `exec`,退出清理用 `down`。这套流程用顺了,不管面对什么项目,心里都有底。