Docker 教學:用 Multi-stage build 建立 Poetry 虛擬環境
文章目錄
from Pixabay
最近都在為〈Python 套件管理器——Poetry 完全入門指南〉一文進行「減肥」,讓它更容易閱讀、回顧。
除此之外,是時候為「Python Poetry」這個系列畫下句點了——而本文就是最後一塊拼圖!
我曾在前文中提到,為什麼不在 Docker 容器中使用 Poetry。主要原因是:
啟動容器後需要先安裝 Poetry 到全域,或打包一個帶有 Poetry 的 image,兩者都會增加新的耦合與依賴,需要更細緻的管理。
但這樣的限制,其實可以透過 Docker 的 multi-stage build 解決。
本文將從實務角度出發,介紹如何使用 multi-stage build,在 Docker 中整合 Poetry。
讓你既能享有 Poetry 的套件管理優勢,又不會對部署流程增加額外負擔。
系列:Python Poetry 三部曲
- Python 套件管理器——Poetry 完全入門指南
- Poetry + pyenv 教學:常用指令與注意事項
- Docker 教學:用 Multi-stage build 建立 Poetry 虛擬環境
我們開始吧!
舊有做法:requirements.txt 及其限制
在過去的容器建構流程中,我們多半會使用requirements.txt
來安裝 Python 套件、建立容器中的專案虛擬環境。
這是因為 Python 的官方 image 已經內建 pip,我們可以在 Docker(Dockerfile)直接透過下列指令安裝相依套件:
1 | RUN pip install -r requirements.txt |
這種做法最大的優勢就是方便。
然而仔細想想,我們在開發階段使用 Poetry,部署時卻用 pip,這樣的流程會產生一個「斷點」。
我們必須從 Poetry 匯出一份與poetry.lock
對應的requirements.txt
,例如:
1 | poetry export -f requirements.txt --without-hashes -o requirements.txt |
這樣做看似簡單、直觀,但經驗告訴我,在更新poetry.lock
時,常常會忘記同步到requirements.txt
。所以 uv 還專門提供了一個 pre-commit hook 來自動化同步。
退一步來說,export
指令產出的結果,其實和 pip 也不是 100% 相容,如果遇到相容問題,將會使得requirements.txt
無法如預期安裝。
真麻煩!
為什麼不直接在 Docker image 中安裝 Poetry?
我們已經知道使用requirements.txt
的方式有其限制,那是否可以直接在 Docker 中安裝 Poetry,來避免 export 同步的麻煩呢?
這似乎是一個簡單粗暴的解法,但除了開頭說的「增加新的耦合與依賴」問題外,這樣做還會導致另一個棘手問題——image 會變得過於龐大。
因為我們必須額外安裝 Poetry 及其所有相依套件(如 curl、build tools 等),這些工具在部署時其實並不需要。
結果是:
- 不必要的工具進入部署環境,增加潛在維護成本。
- Docker image 體積變得難以控管。
而這正是 Docker multi-stage build 要解決的關鍵痛點。
它讓我們可以在前一階段「用完即丟」,只保留執行需要的檔案與相依套件,而非將整套開發工具都一併打包進去。
Multi-stage build 是什麼?為什麼重要?
Docker 的 multi-stage build 允許我們在一個 Dockerfile 中定義多個建構階段。
透過這種方式,我們可以在前一階段中安裝與建構需要的工具(例如 Poetry 與依賴套件),然後在後續階段捨棄它們,只留下必要的執行檔與依賴,大幅減少最終 image 的體積與耦合程度。
對於使用 Poetry 管理套件的 Python 專案而言,這是一種相對理想的建構策略:
- 只在第一階段使用 Poetry,並安裝專案所需的套件。
- 第二階段則不再需要 Poetry——留下建好的 Python 虛擬環境已足。
Multi-stage build 的常見應用場景
Multi-stage build 適用於多種開發與部署情境,以下是幾個常見的應用場景。
1. 編譯型語言的建構階段與執行階段分離
- 在 Go、Rust、C++ 等語言中,編譯階段通常需要安裝大量工具與編譯器。
- 透過 multi-stage build,可以在第一階段完成編譯,第二階段僅保留編譯後的 binary,讓 image 更精簡。
這部分可參考古古的〈為 Spring Boot 生成 Docker image〉,對編譯型語言(文中為 Java)的 multi-stage build 應用,有著更加詳細的講解。
2. 執行環境的依賴管理分離
- Poetry 就是典型範例:建構階段需要 Poetry 處理 dependencies,執行階段只需保留 site-packages 即可。
- pipenv、npm/yarn(JS 世界)也可套用類似概念,第一階段建構,第二階段保留 node_modules 或 dist。
3. CI/CD 中的環境控制與 image 輕量化
- 有些 CI pipeline 在建構階段需要額外工具(如 linters、testing frameworks),但最終部署不需要。
- Multi-stage 可讓這些工具不進入最終 image,保持部署清爽。
Dockerfile 範例與解說
以下範例中,我們將整個建構流程分為兩個階段:
- 第一階段(builder):專責安裝 Poetry 與專案相依套件,不進入最終 image 中。
- 第二階段(runtime):為實際執行環境,僅保留必要的程式碼與套件。
以下是一份簡化過後的 Dockerfile 範例,為你展示如何使用 multi-stage build 來整合 Poetry,同時省略與主題無關的環節。
1 | # 第一階段:建構環境與安裝 Poetry 相依套件 |
重點說明
- 第一階段只負責安裝 Poetry 與相依套件——透過
poetry install
安裝。這裡的 Dockerfile 慣用語是FROM xxx AS yyy
,其中builder
是常見名稱。 - Poetry 安裝套件時,可先調整設定,改成不建立虛擬環境,因為 Docker 本身即為獨立環境,沒必要再隔離。
- 用於部署階段,所以也不必安裝開發、測試相關套件。
- 使用
--only main
來安裝主要的相依套件。
- 第二階段只保留已安裝好的 site-packages 與應用程式程式碼。
- 具體做法就是複製「前階段」產生的檔案、目錄到當前環境。
- 關鍵句則是
COPY --from=builder xxx
。
一切就這麼簡單!
在 AI 時代,重要的是知曉這些概念,至於 Dockerfile 具體該怎麼寫,我認為只要實際操作時一邊詢問 AI 就可以了。
結語
Poetry 是一個強大的 Python 套件管理工具,但若直接將它納入部署環境,可能導致不必要的複雜與耦合。
透過 multi-stage build,我們可以同時兼顧建構效率與部署簡潔。
使用正確策略,Poetry 不只是開發者的利器,也能優雅地融入部署流程。