在前幾篇中,我們介紹了很多功能該怎麼使用,而其中,我想要特別針對volume跟network再多補充一點
volumes 負責解決資料的「持久化」問題,確保我們的資料在容器生命週期結束後依然安然無恙,也就是讓資料活下來。 networks 則負責服務之間的「通訊」問題,讓容器們不再是孤島,而是可以互相溝通、協同作戰的團隊,也就是讓服務聊起來。
今天,我們就來徹底搞懂這兩個關鍵角色。
容器本身是「無狀態 (stateless)」且「短暫 (ephemeral)」的。這意味著當你執行 docker compose down 移除容器時,任何在容器內部檔案系統上發生的變更(例如,資料庫寫入的資料、使用者上傳的檔案)都會隨之煙消雲散。這在開發時或許可以接受,但在生產環境中絕對是一場災難。
volumes 就是為了解決這個問題而生。它讓我們能將一塊由 Docker 管理的儲存空間,或是主機上的一個特定路徑,「掛載 (mount)」到容器內部,從而將資料的生命週期與容器本身脫鉤。
在 docker-compose 中,主要有兩種掛載方式:Named Volumes 和 Bind Mounts。
這是最推薦的方式,特別是用於儲存應用程式產生的資料,例如資料庫檔案、日誌等。
回顧一下我們的 db 服務:
services:
  db:
    # ...
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:
這裡的 db_data:/var/lib/postgresql/data 就是一個 Named Volume。
db_data: 我們為這個 Volume 取的名字。
:: 分隔符。
/var/lib/postgresql/data: 容器內部的路徑,也就是 PostgreSQL 預設存放資料的地方。
我們在檔案最下方透過 volumes: db_data: 來聲明它。這麼做的好處是:
由 Docker 管理:你不需要關心 db_data 到底儲存在主機的哪個實體位置,Docker 會在它自己的特定目錄下(例如 /var/lib/docker/volumes/)管理這塊空間,避免了路徑混亂。
跨平台:無論你的主機是 Linux, macOS 還是 Windows,docker-compose.yaml 檔案都無需修改,Docker 會處理底層差異。
安全且高效:通常比 Bind Mounts 效能更好,也更安全,因為容器內的進程無法輕易修改到主機檔案系統上的任意檔案。
使用時機:當你需要持久化應用程式資料(資料庫、使用者上傳內容、日誌)時,永遠優先考慮 Named Volumes。
Bind Mounts 則是將主機 (Host) 上的一個已存在的檔案或資料夾,直接掛載到容器內部。它的語法是 HOST_PATH:CONTAINER_PATH。
這種方式賦予了我們極大的靈活性,主要用於以下兩種情境:
這是開發時的黃金搭檔。想像一下,你正在開發一個 Node.js 應用,你希望每次修改程式碼後,不用重新 build image 就能立刻看到效果。
# docker-compose.yaml 範例
version: '3.8'
services:
  webapp:
    image: node:18
    # 將目前目錄下的 ./src 掛載到容器的 /app/src
    volumes:
      - ./src:/app/src
    working_dir: /app/src
    command: npm start
    ports:
      - "3000:3000"
在這個例子中,我們將主機上當前路徑下的 src 資料夾,直接映射到容器內的 /app/src。當你在主機上用 VS Code 修改 src/index.js 並存檔時,容器內的 /app/src/index.js 也會即時同步更新。如果你的 Node.js 服務設定了熱重載 (hot-reloading),就能立刻看到變更,極大地提升了開發效率。
另一個常見用途是將設定檔掛載進容器,以覆蓋 image 中預設的設定。例如,你想為一個 Nginx 服務提供一個自訂的設定檔。
# docker-compose.yaml 範例
services:
  proxy:
    image: nginx:latest
    volumes:
      # 將主機的 my-nginx.conf 掛載到容器內 Nginx 的預設設定檔路徑
      - ./my-nginx.conf:/etc/nginx/nginx.conf
    ports:
      - "80:80"
這樣一來,我們就可以在專案目錄下直接維護 my-nginx.conf,而不用為了修改一個設定檔就重新建置整個 Nginx image。
答案是:通常不需要。
當你使用 Bind Mounts 掛載一個資料夾時,如果容器內的目標路徑(如 /app/src)不存在,Docker 會自動為你建立這個資料夾。
但掛載單一檔案時需要注意:如果目標路徑(如 /etc/nginx/nginx.conf)的上層目錄(/etc/nginx)不存在,Docker 不會自動建立,這會導致錯誤。因此,你必須確保掛載的目標路徑是基於 image 中已存在的目錄結構。
預設情況下,當你執行 docker compose up,Compose 會為你的專案建立一個專屬的橋接網路 (Bridge Network),並將該專案下的所有服務容器都連接到這個網路上。
這就是為什麼在我們先前的範例中,adminer 容器可以直接透過服務名稱 db 來找到並連線到資料庫容器。Docker 在這個內部網路上提供了一套 DNS 服務,能將服務名稱解析成對應容器的內部 IP 位址。
了解 Docker 的網路模式對於設計應用架構和排查連線問題至關重要。
隔離性:每個 docker-compose 專案都有自己獨立的 Bridge Network,與主機網路和其他專案的網路是隔離的。這提供了極佳的安全性,A 專案的容器預設無法直接訪問 B 專案的容器。
服務發現:如同前面提到的,在同一個 Bridge Network 內的容器,可以透過「服務名稱」互相訪問。這是微服務架構的基礎。
需要 Port Forwarding:因為網路是隔離的,如果想從外部(例如你的瀏覽器)訪問容器內的服務,就必須透過 ports 關鍵字設定端口轉發 (Port Forwarding),也就是我們熟知的 - "8080:8080"。
注意事項:當你設定 ports: - "8080:8080" 時,意味著將主機 (Host) 的 8080 port 映射到容器的 8080 port。主機上的 port 是獨佔資源,如果你同時啟動了另一個也想使用主機 8080 port 的服務,Compose 會報錯。這時你必須修改其中一個,例如改成 - "8081:8080",然後透過 http://localhost:8081 來訪問。
當網路模式設定為 host 時,容器將不會擁有自己獨立的網路空間。它會直接共享主機的網路介面。
services:
  my_service:
    image: some_image
    network_mode: "host"
優點:
高效能:網路傳輸效能幾乎等同於在主機上直接執行的程式,因為它省去了網路位址轉換 (NAT) 的環節。
無需端口映射:如果容器內的服務監聽 3000 port,那麼你可以直接在主機上透過 localhost:3000訪問它,不再需要 ports 設定。
缺點:
使用時機:適用於對網路效能有極高要求的應用,或者容器需要監聽大量動態 port 的情境。一般業務開發中很少需要用到。
掌握了 volumes 和 networks,你對 Docker Compose 的理解就從「會用」提升到了「精通」。
Volumes:
Named Volumes (my_data:/path):管理應用資料的首選,交給 Docker 處理最省心。
Bind Mounts (./src:/path):開發時同步程式碼、掛載設定檔的絕佳工具。
Networks:
Bridge (預設):提供安全的隔離環境和便捷的服務發現,是 99% 場景下的最佳選擇。
Host:犧牲隔離性換取極致效能,僅在特殊場景下使用。