Select Page

Docker 學習日誌:看懂 docker build,從映像建置到容器更新

學 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.pyAPI 程式
api/requirements.txtPython 套件清單
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,避免操作到另一組服務。

這次學習後,我會在每次更新時依序確認:建置用了哪些檔案、產出了哪個映像、服務是否已使用新映像,以及資料是否放在能持續保存的位置。把這幾件事分開檢查,就比較容易找出更新流程卡在哪一步。

四、刪除 Docker 多餘的 images、containers

服務更新完成後,舊容器、映像與建置快取可能繼續留在主機。我會先確認哪些資源還要保留,再分別清理。停止中的容器不一定是垃圾,有標籤的舊映像也可能是回復版本。以下命令作用於目前連線的 Docker 主機,批次清理並不限於目前資料夾或某個 Compose 專案。

1. 先查看空間、容器與映像

docker system df
docker system df -v
docker ps -a
docker image ls

docker ps -a 會列出包含已停止在內的容器,先核對名稱、狀態與使用的映像。docker system df -v 則能查看較詳細的占用與共享層資訊,不能把每個映像顯示的大小直接相加,當成一定能釋放的空間。詳見 Docker 磁碟使用量說明。

2. 只刪除指定的舊容器

如果只想移除一個已停止、確認不再需要的測試容器,可以使用名稱或 ID。下面的 old-demo-api 是示例,請換成剛才查到的目標:

docker rm old-demo-api

一般 docker rm 不會直接刪除正在執行的容器。若目標仍在執行,先判斷是否真的可以停用,再安排停止。不要為了跳過錯誤就加上 -f,因為它會強制終止容器程序。參考 docker container rm。

刪除容器會失去它的可寫層與相關容器紀錄。如果要保留檔案或除錯日誌,先另外保存。這個範例沒有加 -v,不要求一併刪除掛載的 volume,但也不代表容器內所有資料都已持久化。

3. 批次清除停止中的容器

確認主機上所有停止中的容器都不再需要時,可以改用:

docker container prune

依 官方容器清理說明,這會清除所有停止中的容器,執行前會要求確認。它不會只挑選最新部署留下的舊容器,因此其他專案暫停保留的容器也要先檢查。

若要縮小範圍,也可加上時間條件,例如:

docker container prune --filter "until=168h"

這個例子選取建立時間早於一週前、且目前已停止的容器。until 看的是建立時間,不是「已停止多久」。一個很早建立、剛剛才停止的容器也可能符合條件,時間篩選仍不能代替用途確認。

4. 清除 dangling 映像,或刪除指定版本

先查看 dangling 映像,再決定是否清除:

docker image ls --filter dangling=true
docker image prune

docker image prune 預設清除 dangling 映像。看到 <none> 不代表一定能刪,若映像仍被容器引用,就要先確認該容器用途。只有某個版本不再需要時,也可以指定名稱與標籤:

docker image rm demo-api:old

demo-api:old 是示例標籤,使用前請換成實際版本。若遇到容器引用衝突,先處理引用關係,不要直接強制刪除。依 docker image rm 說明,同一映像有多個標籤時,刪除其中一個標籤可能只會解除該標籤,不一定釋放映像的所有空間。這也不會刪除 registry 上的遠端映像。

5. -a 的範圍比較大,回復用映像要先保留

如果已確認所有未被容器引用的映像都可以移除,才使用:

docker image prune -a

依 官方映像清理說明,-a 會連有標籤、但沒有任何容器引用的映像一起清除。停止中的容器也算引用,不能只看目前正在執行的服務。

清除舊容器後,原本被它引用的映像可能變成可清理對象。替舊映像保留一個 tag,並不能讓它避開 prune -a。需要回復的版本應先確認能從 registry 重新取得,或像前文一樣用 docker image save 留存。離線環境更要先準備回復路徑。

6. Build cache 與 volume 要分開處理

如果空間主要花在建置快取,而不是容器或映像,可以另外查看是否要執行:

docker builder prune

這是清理建置快取的命令,詳見 docker builder prune。清理後,後續建置可能需要重新執行步驟,不能把它當成不影響建置時間的操作。

docker system prune 的範圍更廣,包含停止中的容器、未使用網路、dangling 映像與未使用的建置快取。依 官方 system prune 說明,預設不刪 volume,--volumes 才會再清理未使用的匿名 volume。這篇的目標是清理 images 與 containers,不需要順手加上 volume 清理。

資料庫或上傳檔案放在 volume 時,要先確認備份與用途。容器目前沒掛載某個 volume,不代表裡面的資料已經沒有價值。

7. 清理後,再確認空間與服務

docker system df
docker compose ps
docker compose logs --tail=100 api

先看釋放了哪些空間,再確認目前服務還能正常回應。對我來說,清理的完成條件是移除了確認不再使用的資源,且服務與回復方式都還在,而不是把 Docker 清到什麼都不剩。

本節指令依 Docker 官方文件核對,日期為 2026 年 10 月 5 日。以上是操作示例,沒有在你的主機上執行清理。

雲端現代化:如何將 WordPress 部署至 Cloud Run、Cloud SQL 與 Cloud Storage

在管理多個 WordPress 專案時,傳統 VM 加架構往往面臨擴展性與維護成本的挑戰。透過 Google Cloud Run (Serverless)、Cloud SQL (代管資料庫) 與 Cloud Storage (雲端儲存) 的組合,我們可以建立一個自動縮放、安全且高效率的網站環境。

一、 架構預覽

  • 計算節點:Google Cloud Run (Docker 容器化運行)。
  • 資料庫:Google Cloud SQL (MySQL 8.0)。
  • 靜態檔案:Google Cloud Storage (GCS)。
  • 流量分配:Google Cloud Load Balancing (HTTPS 負載平衡器)。

二、 準備 Docker 鏡像與環境排除

在打包之前,請務必設定 .dockerignore 以優化鏡像體積並保護敏感資訊 。

my-wp-site/
├── Dockerfile           # 自動化打包腳本
├── wp-config.php        # 修改為讀取環境變數的版本
├── .dockerignore        # 排除不需要打包的檔案 (如 .git, local backups)
└── wp-content/
    ├── plugins/         # 放置您自定義的外掛
    └── themes/          # 放置您自定義的主題

建立 標準化 Dockerfile 範本

# 使用官方 PHP-Apache 映像檔,穩定且相容性高
FROM wordpress:php8.2-apache

# 1. 設定環境變數 (Cloud Run 預設監聽 8080,但官方 WP 鏡像預設是 80)
# 這裡我們讓 Apache 監聽 Cloud Run 指定的 PORT
RUN sed -i 's/Listen 80/Listen ${PORT}/g' /etc/apache2/ports.conf
RUN sed -i 's/:80/:${PORT}/g' /etc/apache2/sites-available/000-default.conf

# 2. 安裝必要的系統套件 (如有需要自訂 PHP 擴展可在這加)
RUN apt-get update && apt-get install -y \
    libpng-dev \
    libjpeg-dev \
    && docker-php-ext-configure gd --with-jpeg \
    && docker-php-ext-install gd

# 3. 複製現有的自定義檔案進入容器
# 建議只複製 plugins 和 themes,核心檔案由官方鏡像提供
COPY ./wp-content/plugins/ /var/www/html/wp-content/plugins/
COPY ./wp-content/themes/ /var/www/html/wp-content/themes/
COPY ./wp-config.php /var/www/html/wp-config.php

# 4. 設定正確的檔案權限 (對 WordPress 運行至關重要)
RUN chown -R www-data:www-data /var/www/html

# 5. 設定預設環境變數 (可在部署時被 gcloud 指令覆蓋)
ENV PORT=8080
ENV DB_HOST=127.0.0.1
ENV DB_USER=root
ENV DB_PASSWORD=password

# 暴露埠號
EXPOSE 8080

1. 建立 .dockerignore

Plaintext

.git
.gitignore
.dockerignore
Dockerfile
*.sql
*.zip
.vscode/
wp-config-sample.php

2. 打包與推送鏡像

PowerShell

# 編譯鏡像
docker build -t asia-east1-docker.pkg.dev/[PROJECT_ID]/wp-repo/[docker_name]:latest .

# 推送到 Artifact Registry
docker push asia-east1-docker.pkg.dev/[PROJECT_ID]/wp-repo/[docker_name]:latest

三、 資料庫遷移與設定

1. 匯入 SQL 腳本

將 .sql 檔案上傳至 Google Cloud Storage (GCS) 後執行匯入 。

注意:請確保 SQL 檔案中不含 CREATE DATABASE 或 USE 語句,以免匯入失敗或指向錯誤的資料庫。

PowerShell

gcloud sql import sql [INSTANCE_NAME] gs://[BUCKET_NAME]/[docker_name].sql --database=[docker_name]_db

2. 設定 wp-config.php 智慧判斷

為了同時支援本地開發與雲端環境,建議在 wp-config.php 加入連線判斷邏輯 :

PHP

// 偵測是否在 Cloud Run 環境 (透過 Unix Socket 連線)
if (getenv('INSTANCE_CONNECTION_NAME')) {
    define( 'DB_HOST', ':/cloudsql/' . getenv('INSTANCE_CONNECTION_NAME') );
} else {
    define( 'DB_HOST', getenv('DB_HOST') ?: '127.0.0.1' );
}

// 負載平衡器 HTTPS 辨識
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}

四、 部署至 Cloud Run

部署時需指定 Cloud SQL 連線名稱,這會自動建立加密隧道 。

PowerShell

gcloud run deploy [docker_name] `
  --image asia-east1-docker.pkg.dev/[PROJECT_ID]/wp-repo/[docker_name]:latest `
  --region asia-east1 `
  --allow-unauthenticated `
  --add-cloudsql-instances [PROJECT_ID]:asia-east1:[INSTANCE_NAME] `
  --set-env-vars="INSTANCE_CONNECTION_NAME=[PROJECT_ID]:asia-east1:[INSTANCE_NAME],DB_NAME=[docker_name]_db,DB_USER=root,DB_PASSWORD=[PASSWORD]"

五、 設定負載平衡器 (GCLB) 與自訂網域

為了使用自有的網域(如 blog.rain.tips),建議使用 HTTPS 負載平衡器 。

  1. 建立 Serverless NEG:讓負載平衡器找到 Cloud Run 。
  2. 設定前端 IP:保留一個靜態全域 IP。
  3. Google 管理憑證:在前端設定中新增網域,Google 會自動處理 SSL 簽發與續期 。
  4. DNS 設定:將您的網域 A 紀錄 指向負載平衡器的靜態 IP 。

六、 故障排除 (Troubleshooting)

  • Error establishing a database connection:
    • 檢查 Cloud Run 服務帳戶是否擁有 「Cloud SQL Client」 角色 。
    • 確認 DB_HOST 在雲端環境是否正確指向 :/cloudsql/... 。
  • 503 Service Unavailable:
    • 確認 Cloud Run 服務已設定為 「允許未經驗證的叫用」 。
    • 檢查負載平衡器的憑證是否已變為綠色的 Active 狀態 。
  • IPv6 連線問題:
    • 若使用 Nginx 反向代理遇到 Network is unreachable,請強制 Nginx 優先使用 IPv4 或修改系統 /etc/hosts 。

參考資料

docker – anything – root – password,如果你不清楚docker密碼的處理方法

如果遇到別人包裝好的 docker (99%都是),常會遇到沒有 root 密碼的情形,這時候有幾個解決方法,列舉如下

直接使用 root 的權限進入系統

docker exec -u 0 -it mycontainer bash

或是下面的指令也可以

docker exec -u root -it --workdir / <containerName> bash

修改 root 密碼

passwd root

安裝 sudo

apt-get install sudo
usermod -aG sudo dev

參考資料

https://stackoverflow.com/questions/28721699/root-password-inside-a-docker-container

如何使用 Docker 跟用 command line 一樣

剛開始學習 docker的人,要記很多docker的指令,非常的麻煩以及複雜,身為RD,喜歡一切自己來,自己用CLI來控制,可以參考以下的指令

現有的 Docker 中去執行 shell command

1.找出容器的名稱或是ID

docker ps

2.進入容器

docker exec -it [container_name_or_id] /bin/bash

3.直接執行shell命令,例如更新系統

apt update -y
apt upgrade -y

4.退出容器

exit

延伸閱讀

docker 官網