SSD Nodes Learn 8GB 内存 — 每年 $66
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-01

Docker Compose 如何设置开机自动启动?

为 Compose 服务配置 restart 策略并启用 Docker 开机启动。了解 on-failure 重启后为何不再恢复,以及磁盘、VPN未就绪时何时应使用 systemd。

简短答案

Docker Compose 服务在启动时自动启动,需要同时满足两个条件。Docker 守护进程必须作为系统服务启用,并且文件中的每个服务都必须设置 unless-stoppedalways 重启策略。为每个服务添加 restart: unless-stopped,然后运行一次 docker compose up -d。重启后,容器就会自动恢复运行。对于常见情况,不需要其他配置。

只有在启动顺序很重要时,才需要 systemd 单元:例如,服务栈依赖已挂载的磁盘、VPN 接口或网络共享,而这些资源在 Docker 守护进程启动时尚未就绪。这种情况确实存在,本指南的后半部分会介绍。如果您还不熟悉服务定义和卷,请先阅读 VPS 上的 Docker Compose 基础知识,然后再回来。

在 compose.yaml 中设置重启策略

每个服务需要单独设置一行策略。没有全局开关。因此,如果忘记设置某个服务,重启后该服务会保持停止状态,而其余服务会正常启动。

services:
  app:
    image: nginx:1.27
    restart: unless-stopped
    ports:
      - "8080:80"
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: change-me
    volumes:
      - dbdata:/var/lib/postgresql/data

volumes:
  dbdata:

应用配置,然后从正在运行的容器中读取重启策略:

docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)

该命令会输出 unless-stopped。如果输出 no,说明文件已编辑,但容器从未重新创建。

这是最常见的故障。重启策略存储在容器中,而不是 YAML 文件中。编辑 compose.yaml 不会改变已经存在的容器。docker compose restart 也没有帮助,因为它只会停止并启动同一个容器对象,不会修改其配置。只有 docker compose up -d 会将文件与正在运行的容器进行比较,发现策略已更改后重新创建容器。

如果暂时不想重新创建某个容器,可以直接修改其当前策略:

docker update --restart unless-stopped my-container

同时也要编辑 YAML 文件。docker update 会修改正在运行的容器,而下一次执行 docker compose up -d 时会读取文件,并恢复旧值。

每个 restart 值的实际作用

Docker 定义了 4 个值。它们之间的差异只会在机器重启或 daemon 重启时体现出来。

  • no 是默认值。在任何情况下都不会自动重启容器。
  • always 会在容器停止时重启它。如果您手动停止了容器,那么下一次 Docker daemon 启动时,容器仍会重新运行。这通常会令人意外:您上周主动停止的容器,在重启后又运行了。
  • unless-stopped 的行为类似 always,但手动停止的容器在 daemon 重启后仍会保持停止状态。对于需要偶尔停机维护的服务,应使用此值。
  • on-failure 仅在容器以非零退出代码退出时重启。您可以限制重试次数,例如 restart: on-failure:3

如果某个 stack 应在服务器运行时始终保持运行,unless-stopped 是合适的默认值。只有在希望容器不易长期处于停止状态时,才选择 always

为什么重启后 on-failure 不会继续生效

许多人选择 on-failure,因为它看起来比较谨慎,但第一次重启后却发现所有容器都已停止。原因在其定义中。on-failure 只对一种情况作出响应:容器进程以错误代码退出。

重启不是错误。主机关机时,systemd 会停止 docker.service,守护进程也会主动停止每个容器。容器并未失败,因此该策略没有需要处理的事件。系统恢复运行后,守护进程会检查需要恢复的容器,而正常停止的 on-failure 容器不在其中。它会保持 exited 状态。

您可以直接验证这一点。在某个服务上设置 restart: on-failure,运行 docker compose up -d,重启,然后运行:

docker compose ps -a

该服务会显示为 Exited 状态,状态信息类似 Exited (0) 2 minutes ago。没有任何故障,也不会记录错误,这正是问题难以诊断的原因。该策略的行为完全符合其定义。

on-failure 仍然有用。它适用于执行作业且可能崩溃的容器,此时您希望限制重试次数,并避免进入重启循环。对于需要在重启后保持运行的长期服务,它不是合适的工具。

重启策略仅在 Docker 服务随系统启动时才有效

重启策略由 Docker 守护进程强制执行。如果守护进程未启动,就不会执行任何策略。请检查:

systemctl is-enabled docker
systemctl is-enabled containerd

两项都应输出 enabled。Docker 官方软件源中的软件包会在安装时启用这些服务,因此在全新的服务器上通常会通过检查。如果任一项输出 disabled,请修复:

sudo systemctl enable --now docker containerd

这里有一个值得了解的陷阱。Ubuntu 还提供 docker.socket,它会在某个程序首次访问 Docker API 时按需启动守护进程。用户看到 docker.socket 已启用,便以为守护进程已受管理,然后为节省内存而禁用 docker.service。系统启动时没有程序调用 API,因此套接字从未被访问,守护进程也不会启动。在您输入第一个 docker 命令之前,不会有任何容器启动。套接字激活不能替代启用 docker.service

systemd unit 更适合的情况

重启策略无法处理与系统其他部分之间的启动顺序。守护进程启动后,会尽快启动容器。如果您的堆栈将单独卷、NFS(网络文件系统)共享或加密磁盘中的目录绑定挂载到容器,容器可能会在该路径存在之前启动。Docker 会在挂载点创建一个空目录,然后使用该目录启动容器,而您的数据库会在没有数据的情况下启动。

出现以下任一情况时,请编写 systemd unit。堆栈需要先准备好某个挂载点、VPN 接口或其他 unit。您希望 systemctl stop myappsystemctl start myapp 的行为与系统上其他服务一致。或者,您希望在关机期间正常停止堆栈,而不是让它与守护进程一起被强制终止。如果您不熟悉 systemd unit,编写 systemd 服务和计时器会更详细地介绍文件格式。

编写 systemd 单元

将堆栈放在固定路径中,并置于主目录之外。/srv/myapp 是一个不错的选择,因为在任何人登录前运行的单元不应读取 /home

创建 /etc/systemd/system/myapp.service

[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

启用并启动它:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service

正常的单元会显示 Active: active (exited)。第一次看到时,这看起来不对。实际上这是正确的:Type=oneshotRemainAfterExit=yes 表示该单元已运行其命令,命令已完成,而 systemd 会继续将该单元标记为 active,以便在关机时运行 ExecStop

每一行都有其作用。Requires=docker.service 表示单元会快速失败,而不是在失效套接字上运行 docker composeAfter= 设置执行顺序,因为仅使用 Requires= 并不能做到这一点。RequiresMountsFor= 让 systemd 加载该路径对应的挂载单元并等待它完成。这正是使用单元而不是重启策略的原因。TimeoutStartSec=0 防止在仍在拉取大型镜像时,systemd 终止启动任务。

关于组合使用这两种机制,需要说明一点。Docker 的文档建议不要将重启策略与主机进程管理器混用。该警告针对的是会直接监控容器进程的进程管理器,并在守护进程同时执行相同操作时重启容器进程。Type=oneshot 单元不监控任何进程,因此在 compose 文件中保留 restart: unless-stopped 并同时使用此单元是可以的,而且这正是所需的配置。systemd 负责启动时的顺序,守护进程负责处理凌晨三点崩溃的容器。

通过实际重启进行验证

实际测试无法替代。systemctl restart docker 不会测试挂载顺序,而 docker compose down 后跟 docker compose up -d 完全不会测试启动过程。

sudo reboot

等待系统重启完成,重新连接,然后按以下顺序检查:

uptime
systemctl is-active docker
docker compose ps

uptime 可确认您连接的是一台确实已重启的机器。在堆栈目录中运行 docker compose ps,应列出每个服务,并显示为 running,其运行时间应接近机器的运行时间。显示为 Exited 的服务就是需要检查的对象。

如果某项服务未启动,守护进程日志会覆盖启动时间段:

journalctl -u docker.service -b --no-pager | tail -50

对于由 unit 管理的堆栈,journalctl -u myapp.service -b --no-pager 会显示启动时的完整 docker compose 输出,包括镜像拉取失败或缺少 .env 文件的情况。

会悄悄导致自动启动失效的因素

使用 docker compose run 创建的容器不会继承文件中的重启策略。Compose 会将其视为一次性容器。如果服务似乎忽略了重启策略,请检查它是否使用了 run 而不是 up 启动。

卷或 env_file 条目中的相对路径,会根据 compose 文件所在的目录解析。通过 shell 执行时可以正常工作;通过设置了 WorkingDirectory 的单元启动时也可以正常工作。通过未设置该变量的单元启动时会失败,因为此时工作目录为 /

Rootless Docker 是另一种情况。守护进程以用户服务运行;该用户的最后一个会话结束后,用户服务也会停止。请为该用户启用服务,并允许它在没有用户登录时继续运行:

systemctl --user enable docker
sudo loginctl enable-linger $USER

如果没有 enable-linger,rootless 守护进程会在您注销时关闭,容器也会随之停止。这看起来与重启策略失效完全相同。

还有一点。自动安全更新可能会在固定时间重启服务器。只有在您的堆栈能够自动恢复时,这才是好事。在新机器上完成相关配置,属于首次设置工作的内容之一,参见 新 VPS 上的前 10 分钟

FAQ

restart: always 和 restart: unless-stopped 有什么区别?

当容器自行停止时,两者都会重新启动容器。区别在于您手动停止容器之后的行为。使用 always 时,下次 Docker daemon 启动时容器会再次启动,因此重启会撤销您的手动停止操作。使用 unless-stopped 时,daemon 会记住容器是被有意停止的,并保持其停止状态。除非您明确需要容器保持停止状态,否则请使用 unless-stopped

我添加了 restart: unless-stopped,但容器重启后仍未启动。为什么?

重启策略保存在容器上,而不是文件中。编辑 YAML 不会更新已经存在的容器。运行 docker compose up -d 让 Compose 重新创建容器,然后使用 docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app) 确认。如果输出 no,说明该容器是在您编辑文件之前创建的。另一个常见原因是未启用 docker.service,您可以使用 systemctl is-enabled docker 检查。

如果我已经使用重启策略,还需要 systemd unit 吗?

通常不需要。对于只依赖网络的 stack,重启策略已经足够,而大多数 stack 都属于这种情况。当容器依赖 Docker daemon 启动时尚未就绪的组件时,请添加 unit。例如外部磁盘、加密卷、NFS 共享或 VPN 接口。unit 可以通过 After=RequiresMountsFor= 指定启动顺序,而重启策略无法表达这种依赖关系。

如何永久停止 stack,使其不会在下次重启时恢复?

使用 unless-stopped 时,执行 docker compose stop 即可,因为手动停止的容器不会在 daemon 重启时恢复。使用 always 时,仅停止容器还不够,容器会在重启后恢复。您可以运行 docker compose down 删除容器,或者先使用 docker update --restart no my-container 更改策略。如果 systemd unit 管理该 stack,还要运行 sudo systemctl disable myapp.service,否则 unit 会再次启动它。