在前幾篇中,我們介紹了很多功能該怎麼使用,而其中,我想要特別針對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:犧牲隔離性換取極致效能,僅在特殊場景下使用。