from Pixabayfrom Pixabay

最近都在為〈Python 套件管理器——Poetry 完全入門指南〉一文進行「減肥」,讓它更容易閱讀、回顧。

除此之外,是時候為「Python Poetry」這個系列畫下句點了——而本文就是最後一塊拼圖!

我曾在前文中提到,為什麼不在 Docker 容器中使用 Poetry。主要原因是:

啟動容器後需要先安裝 Poetry 到全域,或打包一個帶有 Poetry 的 image,兩者都會增加新的耦合與依賴,需要更細緻的管理。

但這樣的限制,其實可以透過 Docker 的 multi-stage build 解決。

本文將從實務角度出發,介紹如何使用 multi-stage build,在 Docker 中整合 Poetry。

讓你既能享有 Poetry 的套件管理優勢,又不會對部署流程增加額外負擔。

系列:Python Poetry 三部曲

  1. Python 套件管理器——Poetry 完全入門指南
  2. Poetry + pyenv 教學:常用指令與注意事項
  3. 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 專案而言,這是一種相對理想的建構策略:

  1. 只在第一階段使用 Poetry,並安裝專案所需的套件。
  2. 第二階段則不再需要 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 第一階段:建構環境與安裝 Poetry 相依套件
FROM python:3.12-slim AS builder

# 安裝系統相依套件(Poetry 需要 curl 等工具)
RUN apt update && apt install -y curl build-essential ...

# 安裝 Poetry 並設定環境變數
RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH="/root/.local/bin:$PATH"

WORKDIR /app
COPY pyproject.toml poetry.lock ./
COPY your_module your_module

# 使用 Poetry 安裝相依套件
# 變更設定:不建立虛擬環境,因為 Docker 本身就是隔離環境
RUN poetry config virtualenvs.create false \
# 只安裝 main 的套件,即不含 dev、test 等 group
&& poetry install --only main

# 第二階段:建立乾淨的 image
FROM python:3.12-slim

WORKDIR /app
# 從 builder 階段複製已安裝的 site-packages(重點在這裡)
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .

EXPOSE 8000

CMD ["python", "your_entrypoint.py"]

重點說明

  • 第一階段只負責安裝 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 不只是開發者的利器,也能優雅地融入部署流程。