SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-30

Docker Compose 開機自動啟動設定與 restart policy

了解 Docker Compose 開機自動恢復的設定方式:啟用 restart policy、重建容器套用變更,並釐清 on-failure 為何無法在重開機後持續啟動,以及何時需要 systemd unit。

簡短答案

Docker Compose 服務會在開機時啟動,前提是以下兩個條件同時成立。Docker daemon 必須啟用為 system service,檔案中的每個服務也必須設定 unless-stoppedalways restart policy。為每個服務加入 restart: unless-stopped,執行一次 docker compose up -d,重新開機後容器就會自行恢復。一般情況不需要其他設定。

只有在啟動順序很重要時,才需要 systemd unit。例如,服務堆疊相依於已掛載的磁碟、VPN 介面,或 Docker daemon 啟動時尚未就緒的 network share。這類情況確實存在,本指南的後半部會說明。如果你還不熟悉服務定義與 volume,請先閱讀 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 時,會讀取檔案並將舊值寫回。

重新啟動值的實際作用

Docker 定義了 4 個值。這些值的差異只會在機器重新開機或 daemon 重新啟動時顯現。

  • no 是預設值。在任何情況下都不會自動重新啟動容器。
  • always 會在容器停止時重新啟動容器。如果您手動停止容器,下一次 Docker daemon 啟動時,容器仍會重新啟動。這通常令人意外:您上週刻意停止的容器,重新開機後又會執行。
  • unless-stopped 的行為類似 always,但手動停止的容器在 daemon 重新啟動後仍會維持停止狀態。若服務偶爾需要因維護而停機,應使用此值。
  • on-failure 只會在容器以非 0 結束碼離開時重新啟動。您可以限制嘗試次數,例如 restart: on-failure:3

如果某個 stack 只要伺服器啟動就應保持運作,unless-stopped 是適合的預設值。只有在您希望容器不易維持停止狀態時,才選擇 always

為何 restart: on-failure 無法在重新開機後持續生效

許多人選擇 on-failure,因為它看起來較為謹慎,卻發現第一次重新開機後所有容器都已停止。原因就在定義中。on-failure 只會對一件事做出反應:容器程序以錯誤碼結束。

重新開機不是錯誤。主機關機時,systemd 會停止 docker.service,而 daemon 會刻意停止每個容器。容器並未失敗,因此該原則沒有需要回應的事件。主機重新啟動後,daemon 會檢查需要恢復的容器,而已正常停止的 on-failure 容器不在其中。它會維持在 exited 狀態。

你可以直接驗證。先在服務上設定 restart: on-failure,執行 docker compose up -d,重新開機,然後執行:

docker compose ps -a

該服務會列出 Exited 狀態,並顯示類似 Exited (0) 2 minutes ago 的狀態資訊。沒有任何項目損壞,也不會記錄錯誤,因此很難診斷。該原則完全按照定義運作。

on-failure 仍然有實用價值。它適合執行工作且可能當機的容器,讓你限制重試次數,並避免形成重新啟動迴圈。若要讓長時間執行的服務在重新開機後持續運作,這不是適合的工具。

重新開機策略只有在 Docker 服務於開機時啟動的情況下才有效

重新開機策略由 Docker daemon 強制執行。如果 daemon 未啟動,就不會執行任何策略。請檢查:

systemctl is-enabled docker
systemctl is-enabled containerd

兩者都應輸出 enabled。Docker 官方套件庫提供的套件會在安裝時啟用這些設定,因此在全新的伺服器上通常可以通過檢查。如果任一項輸出 disabled,請修正:

sudo systemctl enable --now docker containerd

這裡有一個值得了解的陷阱。Ubuntu 也提供 docker.socket,會在某個程式首次與 Docker API 通訊時,依需求啟動 daemon。有人看到 docker.socket 已啟用,便以為 daemon 已受到涵蓋,並為了節省記憶體而停用 docker.service。開機時沒有任何程式呼叫 API,因此 socket 從未被存取,daemon 也不會啟動。在你輸入第一個 docker 命令前,不會有任何容器啟動。Socket activation 不能取代啟用 docker.service

systemd unit 是更合適的做法

重新啟動政策無法表達相對於系統其他部分的啟動順序。daemon 會啟動,並在可行時立即啟動容器。如果您的 stack 將獨立 volume、NFS(network file system)分享或加密磁碟中的目錄 bind mount 到容器,容器可能會在該路徑存在前啟動。Docker 會直接在掛載點建立空目錄,並讓容器使用該目錄啟動,導致資料庫在沒有資料的狀態下啟動。

符合以下任一情況時,請撰寫 systemd unit。stack 必須等待掛載、VPN 介面或其他 unit 就緒。您希望 systemctl stop myappsystemctl start myapp 的運作方式與主機上的其他服務一致。或者,您希望在關機期間妥善停止 stack,而不是讓它與 daemon 一起被強制終止。如果您不熟悉 systemd unit,撰寫 systemd service 和 timer 會更詳細說明檔案格式。

撰寫 systemd unit

將堆疊放在固定路徑,且不要放在 home directory 內。/srv/myapp 是合適的選擇,因為在任何人登入前執行的 unit 不應讀取 /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

正常的 unit 會顯示 Active: active (exited)。第一次看到這個狀態時可能會覺得不對。這是正確的:Type=oneshot 搭配 RemainAfterExit=yes 表示 unit 已執行其命令,命令也已完成,而 systemd 會讓 unit 保持 active 狀態,讓 ExecStop 能在關機時執行。

每一行都有其用途。Requires=docker.service 表示 unit 會快速失敗,而不是在 socket 已失效時對其執行 docker composeAfter= 設定執行順序,因為 Requires= 單獨使用時不會設定順序。RequiresMountsFor= 會讓 systemd 載入該路徑的 mount unit 並等待它,這正是使用 unit 而不是 restart policy 的原因。TimeoutStartSec=0 可避免大型 image 仍在 pull 時,systemd 終止啟動工作。

以下說明如何搭配這兩種機制。Docker 的文件建議不要將 restart policy 與 host process manager 混用。該警告針對的是會監督 container process 本身的 process manager;當 daemon 同時嘗試處理相同工作時,它也會重新啟動該 process。Type=oneshot unit 不會監督任何 process,因此在 compose file 中保留 restart: unless-stopped,並搭配此 unit 使用是安全的,也是所需的做法。systemd 負責開機時的執行順序,daemon 則負責處理凌晨 3 點發生的 container crash。

如果要維持執行的是一般長時間執行的 process,而不是 stack,unit 的寫法會不同,因為其下方沒有 daemon;此時必須由 systemd 自己的 Restart= 負責監督。在 systemd 後方以 headless 模式執行 dsh 是這種架構的完整範例,也涵蓋專用使用者與 journal 的設定。

使用實際重新開機驗證

沒有任何測試可以取代實際重新開機。systemctl restart docker 不會測試掛載順序,而 docker compose down 接著執行 docker compose up -d,完全不會測試開機流程。

sudo reboot

請依照以下順序等待、重新連線並檢查:

uptime
systemctl is-active docker
docker compose ps

uptime 可確認你目前查看的確實是已重新開機的機器。接著在 stack 目錄中執行 docker compose ps,應列出每個服務,狀態均為 running,且 uptime 應接近機器的 uptime。顯示為 Exited 的服務就是需要檢查的對象。

如果有服務未成功啟動,daemon 日誌會涵蓋開機期間的資訊:

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

對於由 unit 管理的 stack,journalctl -u myapp.service -b --no-pager 會顯示開機時的完整 docker compose 輸出,包括映像檔拉取失敗或遺失 .env 檔案等訊息。你排程的重新開機就是要監看的那一次,因此可以讓 unit 告知你其他未來的重新開機結果:在 unit 中加入指向 自架 ntfy 伺服器OnFailure= 行,便能將未成功恢復的 stack 轉換為推播通知,而不是等到數天後才發現問題。

容易悄悄導致自動啟動失效的事項

使用 docker compose run 建立的容器不會套用檔案中的 restart policy。Compose 會將它們視為一次性容器。如果服務似乎忽略其 policy,請確認它是否使用 run 啟動,而不是 up

volume 或 env_file 項目中的相對路徑,會以 compose 檔案所在的目錄為基準解析。從 shell 執行時可以正常運作;從設定 WorkingDirectory 的 unit 執行時也可以正常運作。若 unit 未設定該項目,則會失敗,因為此時的工作目錄是 /

Rootless Docker 是另一種情況。daemon 會以 user service 執行,而 user service 會在該使用者的最後一個工作階段結束時停止。請為該使用者啟用服務,並允許服務在沒有使用者登入時持續執行:

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

若沒有 enable-linger,rootless daemon 會在您登出時關閉,容器也會隨之停止。這看起來就與 restart policy 失效完全相同。

還有一點。自動安全更新可能會在固定時間重新開機伺服器;只有在您的 stack 能自行恢復時,這才是好事。在新機器上完成這項設定,應與 新 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 檢查。

如果我已使用 restart policy,還需要 systemd unit 嗎?

通常不需要。對只需要網路的 stack 而言,restart policy 已經足夠,而大多數 stack 都屬於此類。若容器相依於 Docker daemon 啟動時尚未就緒的項目,則應加入 unit,例如外接磁碟、加密磁碟區、NFS 共用或 VPN 介面。unit 可透過 After=RequiresMountsFor= 指定啟動順序,而 restart policy 無法表達這類相依性。

如何永久停止 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 會再次啟動它。