學 Docker 時,很容易先記住一串指令,等到遇上「明明 build 成功,服務卻沒有更新」,才發現還有一些觀念需要釐清。
這篇從一條常見的建置指令出發,記錄映像、標籤、建置範圍與快取的關係,再整理部署時值得注意的七件事。範例使用通用的 Python API 專案名稱,方便套用到自己的環境。
內容目錄
一、先看懂這條指令
假設專案根目錄叫做 my-project,在該目錄執行:
docker build -t demo-api:1 -f api/Dockerfile .
| 指令片段 | 意義 |
|---|---|
docker build | 依照 Dockerfile 建立映像 |
-t demo-api:1 | 將映像命名為 demo-api,標籤設為 1 |
-f api/Dockerfile | 指定要使用的 Dockerfile |
. | 以目前目錄作為建置範圍,也就是 build context |
docker build:依照配方製作映像
Dockerfile 描述建置步驟;image(映像)是產物;container(容器)則是由映像建立、可以啟動的執行個體。
常見指令包括:
FROM:選擇基礎映像,開始一個建置階段。RUN:在建置過程執行命令,例如安裝套件。COPY:將檔案複製到映像內。CMD:設定容器啟動時預設執行的命令。
映像由多個檔案系統層組成,但不能把「每條 Dockerfile 指令」都當成「新增一層檔案」。例如 CMD、ENV 主要記錄設定;FROM 則引入基礎映像。可參考 Dockerfile 官方說明。
-t demo-api:1:給映像一個容易引用的名稱
demo-api 是映像名稱,1 是 tag(標籤)。標籤由使用者自行命名,Docker 不會因為寫了 1,就自動幫你維護版本歷史。
如果再次使用同一標籤建出不同映像,demo-api:1 會改為指向新映像。舊映像若沒有其他標籤,可能顯示為 <none>;若仍被容器引用,就不能直接當作可清除的垃圾。
已存在的容器不會因為 tag 改指向新映像,就自動換版本。
實際發布時,可以改用 demo-api:1.0.1 這類明確標籤,並約定不覆寫已發布版本,讓追蹤與回復更容易。標籤本身是可變的引用,詳見 Docker image tag。
-f api/Dockerfile:選擇配方的位置
在本文這種本機目錄建置方式中,未指定 -f 時,Docker 預設使用 context 根目錄下的 Dockerfile。
-f api/Dockerfile 表示使用子目錄內的檔案,不代表把 build context 改成 api/。
.:決定 COPY 能從哪裡拿檔案
假設專案包含以下內容:
| 路徑 | 用途 |
|---|---|
api/Dockerfile | 建置配方 |
api/main.py | API 程式 |
api/requirements.txt | Python 套件清單 |
shared/ | 共用模組 |
compose.yaml | 服務設定 |
.dockerignore | 建置時要排除的檔案 |
Dockerfile 可以寫:
COPY api/main.py /app/main.py COPY shared/ /app/shared/
這裡一般 COPY 的來源路徑,是從 build context 根目錄算起,不是從 Dockerfile 所在的目錄算起。COPY --from 則是另一種情境,可從其他階段或指定來源複製。
因此,在專案根目錄執行:
docker build -t demo-api:1 -f api/Dockerfile .
如果已經進入 api/,也可以明確把 context 指回上一層:
docker build -t demo-api:1 -f Dockerfile ..
重點是最後的路徑包含哪些檔案。詳見 Build context。
二、建置與部署時要注意的七件事
1. 控制 context 大小,避免帶入不需要的檔案
專案裡可能放著資料集、備份、套件目錄與環境設定。這些東西通常不需要拿來建置。
可以在 context 根目錄放入 .dockerignore:
.git/ .env .env.* !.env.example .venv/ node_modules/ **/__pycache__/ **/*.pyc data/ backups/ *.tar *.tar.gz
請依專案調整:如果建置真的需要 data/ 中的檔案,就不能整個排除。
現代 Docker 常使用 BuildKit,能跳過未使用的檔案,並減少重複傳輸。因此,「整個目錄一定會先全部打包送出」並不是通用的描述。但明確排除無關檔案,仍能減少負擔,也避免 COPY . . 把環境設定或備份帶進映像。參考 BuildKit 與 .dockerignore 說明。
2. 安裝套件可能需要網路,離線部署要事先準備
以下建置步驟通常需要連到套件來源:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir -r requirements.txt基礎映像若尚未存在,也可能需要下載。使用內部套件庫或已備妥的離線套件時,做法則會不同。
若部署環境無法上網,一種常見方式是在可上網、且目標平台相容的準備機建好映像,再匯出:
docker image save -o demo-api_1.tar demo-api:1
把檔案搬到目標主機後載入:
docker image load -i demo-api_1.tar
save 和 load 處理的是映像;Compose 設定、環境變數、掛載檔案與資料庫資料,需要另外準備。若有多個服務,也要備齊各服務使用的映像。參考 docker image save 與 docker image load。
此外,同一份 Dockerfile 在不同日期重新建置,不一定得到完全相同的套件。需要可重現的版本時,應管理套件鎖定檔、基礎映像 digest 與建置來源。參考 Docker 建置最佳實務。
3. Cache 能加速建置,但不會主動檢查套件是否更新
Docker 會依指令、前面的建置狀態與相關輸入,判斷是否能重用快取。
以常見的單階段 Dockerfile 為例,調整順序可以減少重做的工作:
WORKDIR /app COPY api/requirements.txt ./requirements.txt RUN pip install --no-cache-dir -r requirements.txt COPY api/main.py ./main.py COPY shared/ ./shared/
這樣只修改程式碼時,前面的套件安裝步驟通常仍能使用快取;修改套件清單時,才需要重新安裝。參考 快取最佳化。
但 RUN apt-get update ... 或 RUN pip install ... 若命中快取,就不會實際執行,也不會上網確認是否有新版。一般 COPY 的內容或相關檔案中繼資料改變,則可能讓快取失效;只改檔案修改時間並不足以觸發。參考 快取失效規則。
需要停用建置快取時:
docker build --no-cache -t demo-api:1 -f api/Dockerfile .
如果也要嘗試更新基礎映像,可加上 --pull:
docker build --pull --no-cache -t demo-api:1 -f api/Dockerfile .
--no-cache 與 --pull 用途不同。另外,Dockerfile 裡的 pip --no-cache-dir 只控制 pip 的下載快取,不會關閉 Docker 的建置快取。Docker 旗標可參考 Buildx build 說明。
4. 定期看磁碟使用量,再決定清理範圍
多次建置後,可能累積舊映像與 build cache。因為映像之間可以共享層,磁碟占用不一定是每建一次就增加一份完整映像大小。
先查看:
docker system df docker image ls
若要清除未被容器引用的 dangling 映像,可以執行:
docker image prune
這個命令會先要求確認;加上 -f 才會省略確認。-a 會把清理範圍擴大到所有未被容器引用的映像,包含有標籤、可能留作回復用途的版本。
映像清理也不等於清空 build cache,不必把兩者混為一談。參考 docker image prune 與 Build cache 管理。
5. Build 成功後,還要讓服務使用新映像
假設 compose.yaml 中有以下服務:
services:
api:
image: demo-api:1
ports:
- "127.0.0.1:8000:8000"此設定假設應用程式在容器內監聽 0.0.0.0:8000,並將主機端連線限制在本機。api 是 Compose 服務名稱,demo-api:1 是映像名稱,兩者用途不同。
建置完成後,在 Compose 檔案所在目錄執行:
docker compose up -d api
Compose 偵測到映像或設定變更時,通常會重建容器。如果希望明確要求重建,即使沒有偵測到變更,也可以用:
docker compose up -d --force-recreate api
所以,--force-recreate 是強制重建的選項,不是更新映像唯一的方法。參考 docker compose up。
docker compose restart api 則是重新啟動既有容器,不能用來切換到剛建好的映像。參考 docker compose restart。
重建之前,也要知道資料放在哪裡:
| 資料位置 | 重建容器時要注意的事 |
|---|---|
| 容器可寫層 | 刪除舊容器時,裡面的資料會一併失去 |
| 具名 volume | 重新掛載相同 volume 可沿用資料;刪除 volume 另當別論 |
| 主機 bind mount | 資料保留在主機路徑,需確保仍掛載相同位置 |
| 外部資料庫或儲存服務 | 需確認連線設定及新版程式的資料相容性 |
沒有掛載 volume,不代表重建一定安全。 上傳檔案、SQLite 或日誌若只存在容器內,就可能遺失。參考 Docker 儲存說明。
更新後可查看:
docker compose ps docker compose logs --tail=100 api
接著實際呼叫 API 或檢查健康狀態。容器正在執行,與應用程式已正常提供服務,是兩件需要分別確認的事。
6. 搬映像之前,確認 CPU 架構
常見平台包括 linux/amd64(一般 x86-64 主機)與 linux/arm64(ARM64 主機)。
未特別指定時,建置平台通常跟隨 builder 的預設平台。不同架構的單一平台映像,不能假設能在另一台主機原生執行。
可以先檢查映像:
docker image inspect demo-api:1 --format '{{.Os}}/{{.Architecture}}'若 builder 已具備跨平台建置能力,可明確指定目標:
docker buildx build --platform linux/amd64 --load -t demo-api:1 -f api/Dockerfile .
這裡 --load 會將單一平台建置結果載入本機映像儲存區。跨平台的 RUN 步驟可能需要模擬器、原生節點或其他支援,基礎映像與套件也必須支援目標平台。
遇到 exec format error 時,架構不符是排查方向之一,也要檢查執行檔與腳本格式。跨平台原理與前提可參考 Multi-platform builds。
7. 建置時做基本自檢,讓問題早點出現
假設程式需要 requests 套件與 curl 指令,可以在安裝完成後加入:
RUN python -c "import requests" && command -v curl
這種自檢可以提早發現套件漏裝或命令不存在。命令回傳非零狀態時,該次建置會失敗。
但自檢只能涵蓋實際檢查的項目,不能保證部署環境中的資料庫、網路、權限或完整功能都正常。建置時也應避免匯入會立刻連線正式服務的模組。
還有一個容易忽略的地方:這次 build 失敗,之前的同名映像可能仍然存在。 不要只看 docker image ls 有沒有該 tag,就認為這次建置成功。
三、把日常更新整理成固定步驟
以下假設位於專案根目錄,使用前面的 Compose 設定,而且既有容器也是由同一個 Compose 專案建立。
先建置:
docker build -t demo-api:1 -f api/Dockerfile .
確認成功後,再更新服務:
docker compose up -d api
查看狀態與日誌,再驗證實際功能:
docker compose ps docker compose logs --tail=100 api
如果使用 Bash 自動串接建置與更新,可以使用 &&,確保前一步成功才繼續:
docker build -t demo-api:1 -f api/Dockerfile . && docker compose up -d api
若原本使用 docker compose -p demo ... 指定專案名稱,後續操作也要維持相同的 -p demo,避免操作到另一組服務。
這次學習後,我會在每次更新時依序確認:建置用了哪些檔案、產出了哪個映像、服務是否已使用新映像,以及資料是否放在能持續保存的位置。把這幾件事分開檢查,就比較容易找出更新流程卡在哪一步。
近期留言