> ## Documentation Index
> Fetch the complete documentation index at: https://felimet-hub.jmcores.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Layer cache 機制與最佳實踐

> Dockerfile 每層快取怎麼失效、指令排序、cache mount，以及一份可直接抄的最佳實踐 Dockerfile。

`Dockerfile` `layer cache`

理解 layer cache 才知道為什麼 Dockerfile 的指令順序會影響 build 速度。

## 核心原則

* **每條指令是一層**，是 cache 的基本單位。Docker 比對指令字串與相關檔案的 checksum，完全相同才命中快取。
* **失效會串聯**：某一層 cache 失效，後面所有層一律重建，不管它們本身有沒有變。

各指令的失效條件：

| 指令                          | 觸發失效                                                  |
| --------------------------- | ----------------------------------------------------- |
| `FROM`                      | base image digest 改變                                  |
| `RUN`                       | 指令字串改變（**不看實際輸出**，`apt-get update` 抓到新版本不會讓 cache 失效） |
| `COPY` / `ADD`              | 來源檔案的 checksum 改變（mtime 不計）                           |
| `ENV` / `ARG` / `WORKDIR` 等 | 指令字串改變                                                |

## 指令排序最佳化

原則：**變動頻率低的指令放前面，高的放後面。**

```dockerfile theme={null}
# 低效（原始碼改動 → 重新 install 依賴）
COPY . .
RUN pip install -r requirements.txt

# 最佳化（依賴改動才失效）
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
```

`RUN apt-get update` 的雷：`update` 與 `install` 要寫在同一條 `RUN`，否則 `update` 那層被快取後，`install` 可能裝到舊版本：

```dockerfile theme={null}
RUN apt-get update && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
```

## 強制跳過與清理

```bash theme={null}
docker build --no-cache -t myapp .              # 整個 build 跳過快取
docker build --no-cache-filter install -t myapp .   # 只跳過某個 stage
docker builder prune                            # 清掉 build cache
```

## Cache mount（跨 build 持久化套件快取）

讓套件下載快取跨 build 持久化（不進映像層）：

```dockerfile theme={null}
# pip
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# npm
RUN --mount=type=cache,target=/root/.npm \
    npm install

# apt
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    apt-get update && apt-get install -y gcc
```

`--mount=type=cache` 的快取不進 image layer，只在 builder 端持久化，比傳統做法省 build 時間。

## 最佳實踐範本

把原則收成一份可直接抄的範本：

```dockerfile theme={null}
# syntax=docker/dockerfile:1
FROM python:3.12-slim

LABEL org.opencontainers.image.authors="dev@example.com"

# 合併 RUN、裝完清掉 apt 快取，少一層也少佔空間
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 建非 root 使用者
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser

WORKDIR /app

# 相依清單在前，原始碼在後（layer cache 最佳化）
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=appuser:appuser . .

# 切非 root 執行
USER appuser

EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

CMD ["python", "app.py"]
```

重點：合併 `RUN` 並清 apt 快取（`rm -rf /var/lib/apt/lists/*`）、`pip install --no-cache-dir` 不把 pip 快取留進映像、固定基底 tag、切非 root `USER`、用 `.dockerignore` 縮小 context、`WORKDIR` 用絕對路徑取代 `RUN cd`。

## 注意事項與安全

* **別用 `latest`**：基底固定 `major.minor`（或更嚴格用 digest `@sha256:...`），避免基底悄悄換版本。
* **切非 root `USER`**：預設 root 跑容器風險高，建專用使用者再切過去。
* **機密別進 Dockerfile**：`ARG` / `ENV` 會留在 `docker history`，密碼金鑰用執行期注入（`docker run --env` / Compose `env_file` / secrets），不要寫死在 Dockerfile。
* **`ADD` 下載 URL 要當心**：快取與認證行為複雜，需要下載多用 `RUN curl` 並驗 checksum；一般複製優先 `COPY`。

## 接下來

* [Dockerfile 指令逐一](/notes/docker/dockerfile/instructions/)：USER、HEALTHCHECK 等指令細節。
* [配合 Python 程式](/notes/docker/dockerfile/python/)：multi-stage 進一步縮小映像。

官方參考：[docs.docker.com/build/cache](https://docs.docker.com/build/cache/)、[build 最佳實踐](https://docs.docker.com/build/building/best-practices/)
