相依套件
什麼是鎖定檔(lockfile)?
鎖定檔記錄套件管理工具所解析出的每個相依套件的確切版本,包括直接與遞移相依套件,讓下一次安裝能得到相同的相依樹。它是您對建置所能擁有的最佳計畫,但並不是建置實際使用了什麼的紀錄。
更新日期
鎖定檔記錄了什麼
您的 manifest,例如 package.json 或 pyproject.toml,列出您所相依的套件以及您接受的版本範圍。套件管理工具會將這些範圍轉換成整個相依樹的確切版本,包括遞移相依套件,並將結果寫入鎖定檔。
npm 的文件將 package-lock.json 描述為記錄「所產生的確切相依樹,讓後續的安裝能產生完全相同的相依樹」。
package-lock.json 中的單一項目如下所示:
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-...",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
}
}
- version 是所選定的確切版本。
- resolved 是它的擷取來源:registry 的 tarball URL,或是附帶 commit 的 git URL。
- integrity 是該 tarball 的子資源完整性(Subresource Integrity)雜湊值,因此遭竄改的下載內容會驗證失敗。
- dependencies 是它自己的相依套件,相依樹(也就是相依關係圖)就是這樣串接起來的。
各套件管理工具的鎖定檔
| 套件管理工具 | 鎖定檔 | 安裝但不變更鎖定檔 |
|---|---|---|
| npm | package-lock.json | npm ci |
| Yarn (Berry) | yarn.lock | yarn install --immutable |
| pnpm | pnpm-lock.yaml | pnpm install --frozen-lockfile |
| uv | uv.lock | uv sync --locked |
| Poetry | poetry.lock | poetry install |
| Cargo | Cargo.lock | cargo build --locked |
| Bundler | Gemfile.lock | bundle config set frozen true |
| Go modules | go.mod 的版本與 go.sum 的校驗和 |
建置指令預設不會更新 go.mod |
若只使用 pip,常見的替代做法是列出確切版本的 requirements.txt。它會固定所列出的套件,但未列出的套件在每次安裝時都會重新解析。
在 CI 中使用凍結安裝
鎖定檔只有在建置依據它安裝時才有用。一般的 npm install 在
package-lock.json 與 package.json 不再一致時可能會更新鎖定檔,讓建置悄悄解析出新的相依樹。npm ci 則是專為自動化環境設計:
- 它要求必須有
package-lock.json或npm-shrinkwrap.json。 - 若鎖定檔與
package.json不一致,它會以錯誤結束,而不是更新鎖定檔。 - 它會先移除既有的
node_modules。 - 它絕不會寫入
package.json或鎖定檔。
上表中的其他指令,也為各自的套件管理工具執行相同的工作。
應用程式與函式庫
應用程式以及任何您要部署的軟體,都應該 commit 鎖定檔。對函式庫而言,鎖定檔只會鎖定您自己的開發與 CI 環境:npm 從不發布
package-lock.json,而且除了根專案之外一律忽略它。安裝您函式庫的應用程式會在您的版本範圍內自行解析其相依套件。npm 唯一的例外是
npm-shrinkwrap.json,它會隨套件發布並具有優先權,但大多數函式庫都不應該使用它。
為何鎖定檔不是出貨內容的紀錄
鎖定檔是單一專案中單一套件管理工具的計畫。產出您發布版本的建置,做的事情不只是依循這份計畫:
- 建置可能根本沒有使用它。非凍結安裝、缺少鎖定檔,或 monorepo 中的第二份鎖定檔,都代表相依樹的解析方式與您正在查看的檔案不同。
- 快取。從 CI 快取還原的套件不經擷取就直接安裝,而以部分鍵值還原的快取可能早於目前的鎖定檔。請參閱 GitHub Actions 快取隱藏了什麼。
- 建置工具。setup action、編譯器、linter 與程式碼產生器透過其他管道進入建置,並帶有各自的相依樹。
-
下載與安裝腳本。建置步驟中的
curl或套件的安裝腳本,會擷取任何鎖定檔都沒有列出的程式碼,也就是隱藏相依性。 - 基礎映像檔。容器建置會拉取映像檔與作業系統套件,而這些都不在任何程式語言的鎖定檔之內。
完整性雜湊值對上述情況都沒有幫助。它們證明鎖定檔中的套件未遭竄改,卻無法說明建置在鎖定檔之外擷取了什麼。將鎖定檔轉換成 SBOM 的 SCA 工具也承襲了同樣的盲點。
記錄建置,保留鎖定檔
請持續 commit 鎖定檔,並以凍結安裝依據它們安裝。它們能讓建置可重複執行,但光靠鎖定檔並不能構成可重現建置。至於發布版本包含什麼的紀錄,CRACI 記錄的是建置本身:其 GitHub Actions 執行器上能辨識套件的代理伺服器,會記錄每個作業擷取的每個套件,包括從 CI 快取還原的套件,而每個作業的 SBOM 都會標示自身的完整性。比起掃描鎖定檔,這份紀錄能更完整地呈現實際使用中的相依套件。請參閱建置時 SBOM,或預約示範,在您自己的一次建置上比較兩者。
鎖定檔:常見問題
什麼是鎖定檔?
鎖定檔是套件管理工具寫入的檔案,記錄它所解析出的每個相依套件(包括直接與遞移相依套件)的確切版本,以及其來源與校驗和。之後的安裝會讀取它來重現相同的相依樹,而不是重新解析版本範圍。
我應該 commit 鎖定檔嗎?
應該,適用於應用程式以及任何您要部署的軟體。若未 commit 鎖定檔,每次全新安裝都會重新解析版本範圍,並可能引入新的遞移相依套件版本。請 commit 鎖定檔,並在 CI 中以 npm ci 等凍結安裝依據它安裝。
鎖定檔會固定遞移相依套件嗎?
會,這正是它的主要工作。manifest 只列出您的直接相依套件及其允許的版本範圍;鎖定檔則記錄相依樹中每個套件所選定的確切版本。
鎖定檔適用於我發布的函式庫嗎?
對您的使用者而言並不適用。以 npm 為例,它從不發布 package-lock.json,而且除了根專案之外一律忽略它。安裝您函式庫的應用程式,會在您的 package.json 允許的範圍內自行解析其相依套件。
package-lock.json 中的 integrity 欄位有什麼作用?
它保存套件 tarball 的子資源完整性(Subresource Integrity)雜湊值,通常是 sha512。npm 會以它檢查下載的套件,因此已鎖定版本的 tarball 若遭竄改就會安裝失敗。它保護的是鎖定檔中的套件,而不是建置在鎖定檔之外擷取的任何內容。
鎖定檔和 SBOM 一樣嗎?
不一樣。鎖定檔是一項輸入:套件管理工具在單一專案中依循的計畫。SBOM 則是一個發布版本的清冊。SCA 工具常將鎖定檔轉換成 SBOM,前提是建置完全依照鎖定檔安裝,而且沒有安裝其他任何東西。