SSD Nodes Learn 8GB 記憶體 — 每年 $66
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-01

Docker Compose 開機自動啟動設定教學

了解 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。例如,stack 相依於已掛載的磁碟、VPN 介面,或 Docker daemon 啟動時尚未就緒的 network share。這種情況確實存在,本指南的後半部會說明。如果你還不熟悉 service definition 和 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 時,會讀取檔案並還原舊值。

每個 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,而該 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 啟用不能取代啟用 docker.service

systemd unit 是更適合的解決方案

重新啟動原則無法管理與系統其他部分之間的啟動順序。daemon 啟動後,會儘快啟動您的容器。如果您的堆疊將獨立 volume、NFS(network file system)共用資源或加密磁碟中的目錄繫結掛載至容器,容器可能會在該路徑存在之前啟動。Docker 會直接在掛載點建立空目錄,並讓容器使用該目錄啟動,導致資料庫啟動時沒有資料。

符合以下任一情況時,請撰寫 systemd unit。堆疊需要先等待掛載、VPN 介面或其他 unit 就緒。您希望 systemctl stop myappsystemctl start myapp 的運作方式與主機上的其他服務相同。或者,您希望系統關機時能正常停止堆疊,而不是讓堆疊與 daemon 一同被終止。如果您不熟悉 systemd unit,撰寫 systemd service 和 timer 會更詳細介紹檔案格式。

撰寫 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=oneshot 搭配 RemainAfterExit=yes 表示單元已執行其命令,命令也已完成,而 systemd 會繼續將單元標示為啟用中,讓 ExecStop 在關機時執行。

每一行都有其用途。Requires=docker.service 表示單元會快速失敗,而不是在已失效的 socket 上執行 docker composeAfter= 設定執行順序,因為單獨使用 Requires= 並不會設定順序。RequiresMountsFor= 會讓 systemd 載入該路徑的掛載單元並等待它完成;這正是使用單元而非重新啟動原則的完整原因。TimeoutStartSec=0 可避免 systemd 在仍在擷取大型映像檔時終止啟動工作。

補充說明如何結合這兩種機制。Docker 的文件建議不要將重新啟動原則與主機程序管理員混用。這項警告針對的是會監督容器程序本身,並在 daemon 嘗試執行相同工作時重新啟動容器的程序管理員。Type=oneshot 單元不會監督任何程序,因此在 compose 檔案中保留 restart: unless-stopped,並與此單元搭配使用是沒有問題的,而且這正是所需的設定。systemd 負責開機時的執行順序,daemon 則負責處理凌晨三點發生當機的容器。

使用實際重新開機進行驗證

實際測試無可替代。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 的服務就是應檢查的對象。

如果有項目未成功啟動,daemon log 會涵蓋開機期間:

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 的 unit 也有效。對未設定 WorkingDirectory 的 unit 則會失效,因為此時工作目錄是 /

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

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

若沒有 enable-linger,rootless daemon 會在您登出時關閉,容器也會隨之停止。這看起來就完全像是重新啟動原則失效。

最後還有一點。自動安全性更新可能會在固定時間重新啟動伺服器;只有在您的堆疊能自行恢復時,這才是好事。在新機器上完成這項設定,應與 新 VPS 的前十分鐘 中的其他初始工作一併處理。

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 policies,還需要 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 會再次啟動 stack。