文章阅读

先 docker compose down 再 up -d,还是直接 up -d?读完官方文档终于搞懂了

如果你经常用 Docker Compose 部署服务,大概率写过这样一套“肌肉记忆”组合:

docker compose down
docker compose up -d

先把整个项目停掉、删干净,再重新拉起来。这么做当然能跑,但很多人其实说不清楚:docker compose up -d 自己不就会替换旧容器吗?那 down 这一步到底是必要的,还是多余的?

这篇文章基于 Docker 官方文档,把这两条命令各自做了什么、什么时候该用哪个,一次性讲清楚。

官方文档怎么说

docker compose up:自带“变更检测”的创建与启动

官方参考手册对 up 的定义是:构建、重新创建、启动服务的容器,并附着到容器的输出上;加上 -d,也就是 --detach,则让容器转入后台运行。

真正回答我们问题的,是文档里的这段关键描述:

如果某个服务已经存在容器,并且该服务的配置或镜像在容器创建之后发生了变化,docker compose up 会通过“停止旧容器、重新创建新容器”的方式来应用这些变更,同时保留挂载的卷。

换句话说,up 本身就内置了“发现变化 → 移除旧容器 → 换上新容器”的完整逻辑。这正是你平时观察到的“它会自动用新容器替换旧容器”的来源。

而且它很克制:只重建发生了变化的服务,没有变化的容器会原样保留、持续运行,完全不受影响。

围绕这个机制,官方还提供了两个方向相反的开关:

  • --no-recreate:即使发现了变化,也不重建容器。
  • --force-recreate:即使配置和镜像都没有变化,也强制重建容器。

docker compose down:停止并“拆除”整个项目

down 的官方定义是:停止容器,并删除由 up 创建的容器和网络。

默认情况下,它会删除三类东西:

  1. Compose 文件中定义的服务容器;
  2. networks 段里定义的网络;
  3. 项目的默认网络。

不过,声明为 external 的网络和卷永远不会被删除。

数据卷方面,要分两种情况看:

  • 命名卷:默认会被保留,除非显式加上 -v--volumes 才会一并删除。
  • 匿名卷:默认也不会被删除,但官方文档特别提醒了一句很容易被忽略的话:匿名卷没有稳定的名字,所以之后再执行 up 时,新容器并不会自动挂载这些旧的匿名卷。

因此,官方建议:需要在更新之间持久化的数据,应该使用 bind mount 或命名卷,而不要依赖匿名卷。

示意图:down 之后匿名卷与新容器失联

官方入门教程里还有一个非常直观的例子:一个用 Redis 计数的小应用,执行 downup 之后,访问计数会归零。

原因很简单:down 删除了容器,写在容器可写层里的数据也随之消失;而 stop 只是停止容器,容器和数据都还在。

两条路线的本质区别

把上面的信息拼起来,两种做法的差异就清晰了。

直接执行:

docker compose up -d

这是一种原地的、增量的更新方式。

Compose 会逐个服务对比当前配置与运行中容器的状态,只替换有变化的那部分;项目网络保持原样;未被重建的容器连 IP 都不会变;旧容器上的匿名卷数据还会被新容器“接管”。

up 有一个 -V / --renew-anon-volumes 选项,作用是“重新创建匿名卷,而不是从旧容器取回数据”。这个选项的存在,反过来也印证了默认行为就是取回旧数据。

而先执行:

docker compose down
docker compose up -d

这就是一次整栈的推倒重建。

所有容器会先全部停止并删除,项目网络也会被拆掉;然后 up 再从零开始创建网络和全部容器。

这意味着:

  • 整个应用会经历一段完整的停机窗口;
  • 所有容器,包括那些根本没改过的容器,都会换成新的;
  • 网络会被整体重建,容器 IP 会重新分配;
  • 旧容器的匿名卷会彻底“失联”,新容器拿到的是一份空白数据。

示意图:up -d 增量更新 vs down 后整栈重建

维度直接 up -ddownup -d
容器只重建有变化的服务全部删除后重新创建
未变更的服务不受影响,持续运行一并停机、重建
项目网络保持不变删除后重新创建
匿名卷数据新容器接管旧数据随旧容器“失联”,等于丢失
命名卷保留保留,除非执行 down -v
停机范围仅变更的服务短暂中断整栈完整停机一轮

大多数时候,直接 up -d 就够了

改了 compose.yaml 里某个服务的环境变量、端口映射或镜像 tag,或者新增了一个服务——这些日常场景,直接执行:

docker compose up -d

就够了。

Compose 会精确地只动需要动的部分,其余服务毫无感知。这是官方设计的标准更新路径,也是停机最少、最安全的做法。

不过,这里有一个非常高频的坑,也是很多人误以为“up -d 不生效,必须先 down”的真正原因:

up 不会主动去镜像仓库拉取新镜像。

如果你的服务固定使用 myapp:latest 这类不变的 tag,仓库里的镜像更新了,但本地还是旧的,那么在 Compose 看来,“镜像没有变化”,up -d 就什么都不会做。

示意图:tag 不变时 up -d 不会拉取新镜像,需要先 pull

正确的更新姿势是先拉取,再启动:

docker compose pull
docker compose up -d

也可以合并成一步:

docker compose up -d --pull always

如果镜像是本地构建的,则改用:

docker compose up -d --build

镜像拉下来,或重新构建出来之后,Compose 检测到镜像变了,自然会替换对应的容器。整个过程不需要 down 参与。

什么时候才真正需要先 down

1. 改动了网络等顶层资源的定义

Docker 网络不支持原地修改配置。

如果你调整了 compose 文件中网络的子网、驱动等参数,通常需要把旧网络连同挂在上面的容器一起拆掉,才能按新配置重建。

这正是 down 的职责范围。命名卷的定义变更同理。

2. 想要一个彻底干净的环境

排查诡异问题、重置测试数据时,down 能给你一个确定的“零状态”。

如果连持久化数据也要清空,可以再加上 -v,把命名卷一并删除:

docker compose down -v
docker compose up -d

注意:down -v 会删除命名卷,数据无法恢复。

3. 要长时间停用这套服务

如果不只是临时停一下,而是希望释放容器和网络资源,那么 down 本来就是为此设计的。

这种场景甚至不需要紧跟一个 up

4. 需要清理已从 Compose 文件中删除的服务

如果你从 compose 文件里删掉了某个服务,想顺便清理残留容器,down 当然能做到。

但很多时候,更推荐用:

docker compose up -d --remove-orphans

这样同样可以清理孤儿容器,而且不会影响其他仍在运行的服务,通常更顺手。

顺带澄清两个容易混淆的命令

docker compose restart

restart 只是重启容器内的进程。

它不会应用你对 compose 文件所做的任何修改,也不会更换镜像。改完配置之后去执行 restart,等于白改。

这种时候应该用的是:

docker compose up -d

docker compose stop / docker compose start

stop / start 只是停止和恢复容器。

容器本身与其中的数据都会原样保留,适合“暂时关一下,稍后原样恢复”的场景。这也是它与 down 最大的不同。

回到最初的问题

习惯性地 downup -d 并没有错,它永远能得到一个正确的全新状态。

只是大多数时候,这属于“杀鸡用牛刀”:整栈停机更久,网络被重建,匿名卷数据失联。而这些代价换来的效果,up -d 本来就能以更小的动静完成。

一个简单的决策方式是:

  • 日常更新配置或镜像:用 docker compose pull && docker compose up -d
  • 镜像需要本地构建:用 docker compose up -d --build
  • 改了网络等顶层资源、需要彻底清理环境,或打算停用整套服务:再使用 down

参考资料:本文内容主要依据 Docker 官方文档,包括 docker compose up 命令参考docker compose down 命令参考,以及 Docker Compose 快速入门 中关于 downstop 数据持久性差异的说明。

← 上一篇: 不该被 GPT-5.5 淹没的 DeepSeek V4

评论