メインコンテンツまでスキップ

第23章:ビルド時の秘密:BuildKit secretsで“レイヤに残さない”🏗️🤫

ビルドって、ざっくり言うと「手順(RUN/COPY…)のたびに“履歴とレイヤ”が積み上がる」世界です📚🧅 なので、秘密(トークンや鍵)を雑に置くと あとから掘り返せる形で残りがち 😱

この章のゴールはこれ👇

  • ビルドに必要な秘密を、ビルド中だけ 使う(完成イメージに残さない)✨
  • 「やりがち漏えいパターン」を避ける🧨
  • Compose や CI(GitHub Actions)でも同じ発想で運用できるようにする🛡️

1) まず“何がダメか”を体感しよう🙅‍♂️💥

ダメな考え方:Dockerfile に秘密を置く/コマンドに埋める

  • ファイルを COPY すると、そのファイルがレイヤに入る📦
  • ARG/ENV に入れて、それを RUN で使うと、履歴やキャッシュに痕跡が残りやすい🕵️‍♂️

「秘密は“ビルド中だけ見える一時マウント”にする」が正解です✅ この“一時マウント”が BuildKit の secret mount です。 BuildKit では、秘密をビルドコンテナに その RUN の間だけ 一時的に置けます(2ステップ:コマンドで渡す→Dockerfile 側で使う)📌(Docker Documentation)


2) BuildKit secrets の基本ルール(超重要)🔑📏

ルールA:秘密は RUN の中でだけ使う

ルールB:秘密は“ファイル”か“環境変数”として渡せる

  • 元データは「ファイル」でも「環境変数」でもOK。(Docker Documentation)
  • ビルド中のコンテナ内では、既定で「/run/secrets/<id>」に見えます。(Docker Documentation)

ルールC:秘密をログに出さない

  • うっかり表示(cat / echo / set -x)すると、その瞬間に漏れます🫣🧯

3) ハンズオン①:.npmrc(トークン)を“ビルド中だけ”使って npm install する📦🤫

「プライベートパッケージ取得」って、個人開発でも一番漏れやすいポイントです😵 ここを BuildKit secrets で安全にします✨

手順 1:秘密ファイルをリポジトリ外扱いにする🗂️🙈

プロジェクト直下に .secrets を作って、そこに .npmrc を置く想定にします。

  • .secrets/.npmrc(例:中身はトークンを含むので絶対コミットしない)

.dockerignore にも追加しておくと安心です🧯

.secrets

手順 2:Dockerfile(秘密を一時マウントして npm ci)🧪

ポイントは「npm ci を実行する RUN にだけ secret を付ける」です💡

## syntax=docker/dockerfile:1

## 1) 依存取得 + ビルド(dev依存も必要になりがち)
FROM node:24-alpine AS build
WORKDIR /app

COPY package*.json ./

## 秘密はこのRUNの間だけ /root/.npmrc として見せる(レイヤに残さない)
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci

COPY . .
RUN npm run build

## 2) 本番用依存だけ(必要ならここでも秘密を“このRUNだけ”渡す)
FROM node:24-alpine AS prod-deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --omit=dev

## 3) 実行用イメージ(秘密は一切不要)
FROM node:24-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production

COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./

USER node
CMD ["node", "dist/index.js"]
  • 「RUN の間だけ秘密が見える」仕組みが 핵です🔑(Docker Documentation)
  • Node の現行 LTS 系は v24 が Active LTS 扱いなので、ベースに置くと無難です🧠(例として node:24 を使用)(nodejs.org)

手順 3:PowerShell でビルド(秘密を渡す)🪟⚡

docker build --secret id=npmrc,src=.secrets\.npmrc -t myapp:secure .

ここでやっている「秘密をビルドに渡す」操作が公式の基本形です📌(Docker Documentation)

手順 4:漏れてないかチェック(“完成品にない”を確認)🔍✅

## /run/secrets が実行時に存在しない(=ビルド時限定だった)
docker run --rm myapp:secure sh -lc "ls -la /run/secrets || true"

## .npmrc が入ってない(念のため)
docker run --rm myapp:secure sh -lc "find / -maxdepth 4 -name .npmrc 2>/dev/null || true"

4) ハンズオン②:環境変数から secret を渡す(ファイルを作りたくない時)🌱🤫

BuildKit は secret の“元”を環境変数にもできます。(Docker Documentation) 例:API_TOKEN を secret として渡す(ビルド中は /run/secrets/API_TOKEN に見える)🧪

$env:API_TOKEN = "********"
docker build --secret id=API_TOKEN -t myapp:envsecret .

Dockerfile 側は、必要な RUN にだけ付けます👇 (※表示しない!ログに出さない!🫣)

RUN --mount=type=secret,id=API_TOKEN \
node -e "require('fs').readFileSync('/run/secrets/API_TOKEN'); console.log('ok')"
  • 「環境変数→secret」の例も公式に載っています。(Docker Documentation)
  • Dockerfile 側では target(ファイルの置き場変更)や env(環境変数として渡す)もできます。(Docker Documentation)

5) Compose から “ビルド用 secret” を渡す(チーム運用しやすい)🐳🧩

Compose には build.secrets という「このサービスのビルドにだけ秘密を許可する」仕組みがあります。(Docker Documentation) 短い書き方ならこんな感じ👇

services:
app:
build:
context: .
secrets:
- npmrc

secrets:
npmrc:
file: ./.secrets/.npmrc
  • build.secrets は「サービスのビルド単位で、秘密のアクセス権を付ける」発想です🔐(Docker Documentation)
  • Dockerfile では RUN --mount=type=secret,id=npmrc,... を使えばOKです。(Docker Documentation)

さらに「渡し忘れたら失敗してほしい」場合は required=true も使えます(Compose の例にも出てきます)🧨🧯(Docker Documentation)


6) CI(GitHub Actions)での secrets:仕組みは同じ🧪🤖

CI でも「ビルド中だけ secret を渡す」が基本です。 Docker の GitHub Actions 向けドキュメントでは、workflow から secrets 入力で BuildKit secret mount を渡す例が示されています。(Docker Documentation)

(ここで大事なのは「CI の secret を Dockerfile に埋めない」こと!🚫)


7) よくある落とし穴(ここ踏む人多い)🕳️😵‍💫

  • 秘密を COPY しちゃう → レイヤに入って終わり📦💀
  • RUN の中で秘密をファイルに書き出して残す → “残る”ので意味が薄い🧨
  • ログに出す(cat / echo / set -x) → その場で漏れます🫣
  • 必要ない RUN にまで secret を付ける → 被害半径が広がる🗺️💥
  • AI が提案した「ARG で入れよう」を採用 → 便利そうに見えて危険なことが多い⚠️🤖 👉 “secret mount で RUN の間だけ”に戻そう、が正解です✅(Docker Documentation)

8) 仕上げ:第23章チェックリスト✅🧾✨

  • 秘密は Dockerfile に書いてない(COPY/ARG/ENV で焼き込んでない)🚫
  • 秘密を使うのは「その RUN だけ」(mount=type=secret)🧪(Docker Documentation)
  • ログに出してない(デバッグでも出さない)🫣
  • .secrets は gitignore + dockerignore 済み🙈
  • できれば required=true で渡し忘れを事故にしない🧯(Docker Documentation)
  • 完成イメージ内に秘密が無いことを find 等で確認した🔍✅

次の章(第24章)が「private repo や社内パッケージ(SSH/トークン)」なので、 この第23章で作った“secret を一時マウントする型”がそのまま効いてきますよ〜!😄🔑✨