Docker Compose存在一個認知上的問題。如果你問一群平臺工程師對它的看法,你很可能會聽到類似這樣的回答:“它非常適合本地開發使用,但我們在實際工作中會使用Kubernetes。”

我理解這種觀點。多年來我也一直持這種看法。Compose只是我在筆記本電腦上用來創建Postgres數據庫的工具,并不認為它可以用于測試環境,更不用說那些需要GPU支持的工作負載了。

然而,2024年和2025年發生了變化。Docker推出了一系列功能,這些功能讓Compose從一個僅為開發者提供便利的工具,變成了能夠處理復雜部署場景的工具。配置文件允許你通過一個文件來管理多個環境;“觀察模式”徹底解決了導致基于容器的開發效率低下的重建循環問題;GPU支持為機器學習工作負載打開了大門;而一系列其他的小改進(如更完善的健康檢查機制、與Bake工具的集成、結構化的日志記錄功能)也彌補了之前讓Compose顯得不夠強大的那些缺陷。

在這篇文章中,我將介紹以下內容:如何使用Docker Compose配置文件來管理多個環境;如何設置“觀察模式”以實現開發過程中的實時代碼同步;如何為機器學習工作負載配置GPU支持;如何正確配置健康檢查機制和啟動順序,以避免服務在初次啟動時出現故障;以及如何利用Bake工具來銜接本地開發流程與生產環境的構建過程。同時,我也會指出Compose仍然存在的不足之處,以及在這些情況下應該選擇其他工具來使用。

先決條件

你應當已經熟悉Docker的基本知識,并且之前曾經編寫過compose.yaml文件。你需要安裝Docker Compose v2版本。具體需要哪個最低版本,取決于你想要使用哪些功能:service_healthy依賴條件要求使用v2.20.0+版本,“觀察模式”需要v2.22.0+版本,而gpus:配置選項則要求使用v2.30.0+版本。你可以運行docker compose version命令來檢查自己當前安裝的是哪個版本。

目錄結構

現代的Compose文件:發生了哪些變化

如果你最近沒有查看過Compose文件,首先會注意到version字段已經消失了。Docker Compose v2完全忽略了這個字段,如果仍然包含它,系統會發出棄用警告。現代的compose.yaml文件直接從描述服務的內容開始,不需要任何前置說明。

但結構上的變化遠不止這些。對于典型的Web應用程序棧來說,現代的、適用于生產環境的Compose文件應該如下所示:

services:
  api:
    image: ghcr.io/myorg/api:${TAG:-latest}
    env_file: [configs/common.env]
    environment:
      - NODE_ENV=${NODE_ENV:-production}
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  db:
    image: postgres:16-alpine
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

volumes:
  db-data:

看看其中包含的內容:資源限制、帶有依賴條件的健康檢查機制、合理的卷管理功能。這些并不是可有可無的功能,它們正是讓Compose能夠在筆記本電腦之外也能正常使用的關鍵要素。

尤其是健康檢查功能,它解決了Compose最古老也是最令人困擾的問題之一:即Web服務器在數據庫尚未準備好接受連接之前就啟動了。如果你曾經在啟動腳本中添加過sleep 10這段代碼,并且祈禱它能正常工作,那你肯定能理解我的意思。

如何使用配置文件來管理多種環境

正是這個功能徹底改變了我對Compose的使用方式。在配置文件出現之前,管理不同環境意味著必須在兩種繁瑣的方法之間做出選擇:要么維護多個Compose文件(如docker-compose.ymldocker-compose.dev.ymldocker-compose.test.ymldocker-compose.prod.yml),并處理它們之間不可避免的差異;要么使用一個龐大的文件,在其中根據不同的環境需求來注釋掉某些服務的配置。這兩種方法都存在缺陷,而且都會導致“在我的機器上可以正常運行,但在其他環境中卻不行”這類問題。

配置文件為人們提供了一條更加簡潔的解決方案。你可以將服務分配到不同的組中:沒有配置文件的 service 會始終被啟動;而帶有配置文件的 service 只有在明確激活了相應的配置文件后才會被啟動。你也可以通過環境變量COMPOSE_PROFILES來激活配置文件,而無需使用命令行參數,這對于持續集成流程來說非常方便(具體語法請參閱官方文檔)。

下面是一個具體的示例:

services:
  api:
    image: myapp:latest
    # 沒有配置文件 = 始終被啟動

  db:
    image: postgres:16
    # 沒有配置文件 = 始終被啟動

  debug-tools:
    image: busybox
    profiles: [debug]
    # 只有在使用--profile debug參數時才會被啟動

  prometheus:
    image: prom/prometheus
    profiles: [monitoring]
    # 只有在使用--profile monitoring參數時才會被啟動

  grafana:
    image: grafana/grafana
    profiles: [monitoring]
    depends_on: [prometheus]

現在,你們的團隊可以使用簡單且容易記住的命令來開展工作:

# 開發環境:僅使用核心服務棧
docker compose up -d

# 帶有監控功能的開發環境
docker compose --profile monitoring up -d

# 持續集成環境:僅使用核心服務棧(不包含監控功能)
docker compose up -d

# 完整的服務棧環境,支持調試功能
docker compose --profile debug --profile monitoring up

只需要一個 Compose 文件,無需擔心配置沖突,也無需猜測應該使用哪個配置文件。

我實際使用過的配置模式

有四種模式是我經常使用的:

“僅基礎設施”模式。這種模式適用于那些在本地主機上運行應用程序代碼,但需要將數據庫、消息隊列和緩存等基礎設施服務放在容器中的開發人員。在這種情況下,可以將基礎設施服務配置放在單獨的配置文件中,而將應用程序服務配置放在另一個文件中。后端開發人員只需運行 docker compose up 命令,就可以獲取 Postgres 和 Redis 等服務,然后直接在本地主機上啟動應用程序,并使用他們喜歡的調試工具進行測試。

“模擬環境與真實環境”模式。可以在 dev 配置文件中配置 payments-mock 服務,在 prod 配置文件中配置真實的支付網關服務。使用同一個 Compose 文件,根據不同的配置環境,系統的行為會完全不同。這種模式多次幫助我的團隊避免了在開發過程中不小心調用真實支付 API 的情況。

“持續集成優化”模式。像 Selenium 瀏覽器這樣的復雜服務以及監控相關組件可以被放在單獨的配置文件中,這樣持續集成流程就可以跳過這些部分。這樣一來,測試套件的運行速度會更快,而且只有在進行端到端集成測試時才會加載這些服務。

“人工智能/機器學習工作負載”模式。依賴 GPU 的服務(如推理服務器、模型訓練容器)可以被放在 gpu 配置文件中。沒有 GPU 的開發人員也可以繼續使用其他服務,而不會遇到任何問題。

有一個實用的建議可以幫你們避免很多麻煩:在項目的 README 文件中詳細說明各種配置文件的用途。這聽起來似乎很顯而易見,但當新成員運行 docker compose up 命令時,如果他們不明白為什么監控面板無法啟動,那么這個文檔就能為他們提供答案。只需制作一個表格,列出每個配置文件及其包含的內容,就可以避免每次新成員加入團隊時都有人重復提出同樣的問題。

如何使用監視模式來快速結束重建過程

如果配置文件解決了環境管理方面的問題,那么監視模式則解決了開發人員在使用這些工具時的體驗問題。

你們可能熟悉傳統的基于容器的開發工作流程:修改代碼后,運行 docker compose build 命令構建容器鏡像,然后運行 docker compose up 啟動容器進行測試。如果發現錯誤,就需要再次修改代碼并重新構建容器,之后再重新進行測試。每次這樣的循環都會耗費 30 秒到 1 分鐘的時間。在一天繁忙的開發工作中,僅僅因為等待構建日志的滾動而浪費的一個小時甚至更長時間,實在是非常可惜的。

監視模式(在Compose 2.22.0版本中首次引入,并在后續版本中得到了顯著改進)會監控您的本地文件,當這些文件發生變化時,它會自動采取相應的行動。該模式支持三種同步策略,為不同情況選擇合適的策略是確保其正常運行的關鍵。如果您想深入了解相關細節,官方的監視模式文檔提供了完整的規范說明。

sync命令會將發生變化的文件直接復制到正在運行的容器中。這種同步方式對于Python、JavaScript、Ruby這類解釋型語言,以及React、Vue、Next.js這類支持熱模塊重載的框架來說效果最佳。文件一旦被復制到容器中,相關框架就會立即檢測到這些變化,用戶的瀏覽器也會隨之更新內容——無需重新構建應用程序或重啟容器。不過,如果您使用的是Go、Rust或Java等需要編譯的語言,sync命令就無法發揮作用,因為這些語言的代碼需要重新編譯才能生效。在這種情況下,請使用rebuild命令。

rebuild命令會觸發整個鏡像的重建以及容器的重啟。當您修改了package.jsonrequirements.txt等依賴配置文件,或者直接修改了Dockerfile本身時,就需要使用這個命令。因為在這種情況下,僅僅同步文件是遠遠不夠的,必須重新生成一個新的鏡像才能確保應用程序能夠正常運行。

sync+restart命令會先將文件復制到容器中,然后再重啟主進程。這種模式非常適合用于配置文件的修改,比如調整nginx.conf或數據庫配置文件。在這種情況下,應用程序需要重新加載才能應用新的設置,但鏡像本身并不需要被重新生成。

以下是一個針對Node.js應用程序的實際監視配置示例:

services:
  api:
    build: .
    ports: ["3000:3000"]
    command: npx nodemon server.js
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
          ignore:
            - node_modules/
        - action: rebuild
          path: package.json
        - action: sync+restart
          path: ./config
          target: /app/config

您可以通過docker compose up --watch命令來啟動這個監視模式,或者單獨運行docker compose watch命令,這樣就可以將文件同步事件與應用程序的日志分開處理。

在設置監視模式之前,有幾點需要注意:該模式僅適用于那些具有本地build:配置的服務。如果您是從注冊中心拉取預構建的鏡像,那么Compose就沒有什么可以同步或重建的內容了,因此監視模式會忽略這類服務。此外,您的容器還需要安裝一些基本的文件操作工具(如statmkdir),并且容器的USER賬戶必須具有對目標路徑的寫入權限。如果您使用的是像scratchdistroless這樣的最小化基礎鏡像,sync命令也是無法正常使用的。最后,如果您使用的是較舊版本的Compose,請先確認哪些同步操作是受支持的:sync+restartsync+exec這兩個命令是在初始版本2.22.0之后才在后續的次要版本中添加的。

這是一個巨大的改進。修改源文件并保存后,對于支持熱重載的框架來說,更改幾乎會在一秒鐘內就生效。無需切換上下文來執行構建命令,也無需等待,只需編寫代碼即可。

觀察模式與綁定掛載

你可能會問一個合理的問題:多年來,綁定掛載就已經提供了一種實時重載的功能,那么為什么還需要觀察模式呢?

雖然綁定掛載確實有效,但它們存在一些特定于平臺的問題,這些問題長期以來一直困擾著Docker Desktop。在macOS和Windows系統中,綁定掛載需要通過主機操作系統與運行Docker的Linux虛擬機之間的文件系統共享層來工作。這種機制會導致權限設置上的問題、在處理大型目錄時會出現性能瓶頸(你是否曾經見過macOS系統下的node_modules文件夾導致綁定掛載無法正常工作?),而且文件通知機制的不穩定性也會使熱重載功能變得不可靠。

觀察模式通過在應用程序層面直接同步文件來規避這些問題。它的運行更加穩定,能夠在不同平臺上保持一致的行為,并且讓你能夠更好地控制文件發生變化時會發生什么。

不過,對于許多使用場景來說,綁定掛載仍然非常適用,尤其是在使用原生Linux系統的環境中,因為這種情況下不會存在性能上的開銷。而對于那些遇到了跨平臺問題的團隊來說,或者那些需要自動重建和重啟功能的人來說,觀察模式才是更好的選擇。

如何為機器學習工作負載配置GPU支持

正是這個功能讓我重新思考了Compose所能實現的功能。

多年來,Docker一直通過NVIDIA Container Toolkit以及--gpus參數支持單個容器的GPU直通功能。但在Compose配置文件中設置GPU訪問權限時,過去往往需要使用一些繁瑣的運行時聲明方式,這些聲明方式的文檔資料很少,而且還會隨著Compose版本的更新而發生變化。有時候你可能會找到2021年發布的Stack Overflow答案,嘗試按照它來操作,卻發現后來已經不再適用了。

現代版的Compose規范通過deploy.resources.reservations_devices塊來簡潔地解決這個問題:

services:
  inference:
    image: myorg/model-server:latest
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

如果你使用的是Compose v2.30.0或更高版本,還可以使用更簡潔的gpus:字段來配置:

services:
  inference:
    image: myorg/model-server:latest
    gpus:
      - driver: nvidia
        count: 1

這兩種方法的作用是相同的。deploy.resources語法在較早期的Compose版本中也同樣適用,并且能讓你擁有更多的控制權(比如可以通過設置device_ids來指定使用特定的GPU)。而當只需要簡單的GPU訪問功能時,gpus:這種簡寫方式會更加方便。

如果你忽略了這個配置步驟,很可能會遇到問題:在所有這些設置生效之前,你的主機機器必須安裝了正確的GPU驅動程序以及。首先在主機上運行nvidia-smi命令,如果該命令無法顯示你的GPU信息,那么Compose也無法識別它們。對于需要使用CUDA的工作負載來說,應該使用官方的GPU基礎鏡像,比如或者PyTorch/TensorFlow對應的GPU鏡像。Compose GPU訪問配置指南會為你提供詳細的設置步驟。

就是這么簡單。當你運行 `docker compose up` 時,推理服務會使用一塊 NVIDIA GPU。如果你希望所有可用的 GPU 都被分配給該服務,可以將 `count` 設置為 `”all”`;或者通過 `device_ids` 來指定將特定的 GPU 分配給特定的服務。

如何結合配置文件來使用多 GPU 資源

在這里,配置文件與 GPU 支持的功能就能發揮出極大的作用。以一個機器學習工作負載為例:你需要一個用于文本生成的 LLM、一個用于向量搜索的嵌入模型,以及一個向量數據庫:

services:
  vectordb:
    image: milvus/milvus:latest
    # 在 CPU 上運行,因此不需要配置文件

  llm-server:
    image: ollama/ollama:latest
    profiles: [gpu]
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids: ["1"]
              capabilities: [gpu]
    volumes:
      - model-cache:/root/.ollama

  embedding-server:
    image: myorg/embeddings:latest
    profiles: [gpu]
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids: ["0"]
              capabilities: [gpu]

沒有 GPU 的開發人員只需使用 `docker compose up` 即可開始編寫應用程序邏輯。向量數據庫會正常啟動,他們也可以通過其 API 進行編程開發,一切都會順利進行。當需要測試整個機器學習流程時,擁有多 GPU 工作站的人員就可以運行 `docker compose --profile gpu up`,這樣系統就會使用指定的 GPU 資源來運行。

這種模式已經成為我們 AIOps 平臺開發的核心。負責構建警報邏輯的團隊并不需要 GPU,而那些用于訓練異常檢測模型的團隊則確實需要 GPU。同一個 Compose 文件就可以同時滿足這兩支團隊的需求。

如何配置健康檢查、依賴關系以及啟動順序

Compose 中一個被人們忽視但非常實用的功能就是它對服務依賴關系的處理方式。`depends_on` 指令現在支持那些具有實際意義的條件設置(這需要使用 Compose v2.20.0 及更高版本,具體細節請參閱 啟動順序配置文檔):

depends_on:
  db:
    condition: service_healthy
  redis:
    condition: service_started

當你將這一機制與適當的健康檢查結合使用時,就能避免許多 Compose 配置中普遍存在的“等待 10 秒再嘗試”的問題。你的 API 服務會等到 PostgreSQL 確實開始接受連接請求后才會嘗試啟動——不僅僅是確保容器已經運行起來,還要保證其中的數據庫進程通過了健康檢查。

有一點需要注意:請仔細調整 `start_period` 的值。像 PostgreSQL 這樣的數據庫在首次啟動時需要一定的時間來初始化,尤其是當它們正在執行數據遷移操作時。如果沒有設置 `start_period`,健康檢查會立即開始重試檢測,從而導致服務在還未完成啟動流程就被判定為“不健康”。對于大多數數據庫服務來說,這樣的配置都是非常合適的。

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 5s
  timeout: 2s
  retries: 10
  start_period: 30s

start_period為容器提供了30秒的緩沖時間,在這段時間內,失敗的健康檢查不會被計入重試次數中。

這看似只是一個細節,但如果你曾經開發過由八到十個相互關聯的服務組成的系統,你就會知道在系統初次啟動時,調試這些服務之間的連鎖故障會浪費多少時間。正確的啟動順序能夠避免這些問題,使你的本地開發環境更接近生產環境的實際運行狀態。

如何使用Bake來構建生產環境的鏡像

我之前提到過Bake的集成功能,它確實值得單獨介紹,因為一旦你開始在本地開發之外使用Compose,就會遇到這樣一個問題:你的開發用Compose配置文件與生產環境下的構建流程有著不同的需求。

在開發階段,你需要快速構建鏡像、利用本地緩存,并生成適用于單一平臺的鏡像;而在生產環境中,則需要將帶有標簽的鏡像推送到注冊中心、進行多平臺構建,并確保構建過程的可驗證性。如果試圖將這兩種需求都融合到同一個compose.yaml文件中,很快就會導致配置混亂。

Docker Bake(docker buildx bake)能夠讀取你的compose.yaml文件并據此生成構建目標,但你也可以通過單獨的docker-bake.hcl文件來覆蓋或擴展這些配置。這樣既能保持開發工作流程的整潔性,又能讓持續集成系統獲得所需的配置選項。Bake的相關文檔詳細介紹了HCL語法以及與Compose的集成方式。

以下是一個簡單的docker-bake.hcl示例:

group "default" {
  targets = ["api", "worker"]
}

target "api" {
  context    = "api"
  dockerfile = "Dockerfile"
  tags       = ["registry.example.com/team/api:release"]
  platforms  = ["linux/amd64"]
}

target "worker" {
  context    = "worker"
  dockerfile = "Dockerfile"
  tags       = ["registry.example.com/team/worker:release"]
}

這樣,你的持續集成管道會執行docker buildx bake來生成發布用鏡像,而開發人員則可以在本地繼續使用docker compose up --build進行構建。這兩種工作流程雖然使用相同的Dockerfile,但在需要時會有不同的配置設置。

我總結出的最佳實踐是:在本地開發和持續集成測試環境中使用Compose,在持續集成過程中使用Bake來生成發布鏡像,然后將這些鏡像推送到團隊所使用的部署目標上(比如預發布服務器、Kubernetes集群或邊緣節點)。Compose能幫助你快速將代碼轉化為可運行的容器;而Bake則能確保生成的鏡像帶有正確的標簽,并具備必要的驗證信息,從而使其可以直接用于生產環境。

Compose的局限性

在這篇文章中,我一直在強調Compose已經發展得非常成熟了。但我也應該告訴你它有哪些不足之處。最好現在就由我來告訴你這些,而不是讓你在生產環境中親身體會到它們帶來的麻煩。

Compose并不是一種用于管理容器部署的工具。它不會在多臺主機之間調度任務,也不會自動進行故障轉移。它無法實現零停機時間的滾動更新功能,也不支持服務網格網絡技術。如果你需要這些功能,那么Kubernetes、Nomad或Docker Swarm才是更合適的選擇。

Compose并不能替代Helm或Kustomize。如果你正在使用Kubernetes進行部署,Compose文件并不能直接轉換為Kubernetes所需的配置文件。雖然Docker提供了Compose Bridge來幫助轉換這些文件,但該工具仍處于測試階段,無法處理一些復雜的Kubernetes特定配置,比如自定義資源定義或入口規則等。

在生產環境中,Compose在處理敏感信息方面表現不佳。盡管Compose提供了相關功能,但其安全性相比HashiCorp Vault、AWS Secrets Manager或Kubernetes自帶的秘密管理機制仍然有限。對于非測試環境而言,你顯然需要使用外部 secret 管理工具。

現代環境中,Compose最適合用于本地開發環境、CI/CD測試環境、單節點測試環境,以及那些適合使用高性能機器進行部署的工作負載場景(尤其是需要GPU的計算任務)。在這些范圍內,Compose確實非常有用;但一旦超出這些范圍,使用Compose就會遇到諸多問題。

如果你確實在測試環境或單節點生產環境中使用Compose,還有幾項配置是值得添加的:為每個服務設置restart: unless-stopped選項,這樣在主機重啟后容器能夠自動重新啟動;配置日志記錄機制,確保日志能夠被妥善保存而不會消失在docker logs中;同時還要為命名卷制定備份策略。這些雖然不是Compose特有的問題,但Compose也無法為你解決它們。

實際應用建議

如果你目前使用的是基礎的Compose配置,并且想要開始利用這些高級功能,以下是我推薦的步驟順序。每個步驟都是逐步進行的,而且相互兼容,單獨執行也是很有意義的。你不需要一次性完成所有這些配置。

第1周:添加健康檢查機制以及正確的depends_on條件設置。僅做這些就能有效解決最常見的問題:由于依賴項尚未準備好,導致服務在啟動時崩潰。首先從數據庫服務和主應用服務開始配置condition: service_healthy條件,你會立刻看到效果的變化。

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 5s
  timeout: 2s
  retries: 10
  start_period: 30s

第2周:引入配置文件模板。將監控工具配置放在monitoring模板中,調試工具配置放在debug模板中,然后刪除那些多余的Compose配置文件。使用統一的配置文件模板,而不是四份內容幾乎相同但又不完全一致的文件,會讓管理變得更加簡單。

第3周:為您最常編輯的服務啟用監控模式。選擇那些開發人員花費最多時間進行迭代修改的服務,首先在這些服務上啟用監控模式。一旦團隊看到實際效果——保存文件后變化能在不到一秒的時間內顯現出來——他們就會要求在其他所有服務上也使用這種功能。

第4周:設置資源限制。為每一項服務設定內存和CPU的使用上限。這樣就可以防止某個容器因資源消耗過多而影響其他容器的正常運行,同時也能讓您真實地了解這些服務在實際生產環境中的表現。此外,這一措施還有助于及時發現內存泄漏問題。

deploy:
  resources:
    limits:
      memory: 512M
      cpus: "1.0"

總結

2026年的Docker Compose已經不再是幾年前的那個工具了。配置文件、監控模式、對GPU的支持、完善的依賴管理功能以及與Bake工具的集成,使得它能夠處理那些規模較為復雜的工作負載——只要這些工作負載能夠在單個節點上運行即可。

它雖然不是Kubernetes,但也不應該試圖成為Kubernetes的替代品。然而對于本地開發、持續集成流程、測試環境以及單臺機器上的GPU相關任務來說,Docker Compose已經成為了不可或缺的工具。如果您曾經因為它的過去而忽視了它,那么現在的這個版本確實值得您重新考慮一下。

如果這篇文章對您有幫助,您可以在我的博客上找到我關于DevOps、容器技術以及自動化運維最佳實踐的更多文章。

Comments are closed.