第24章:private repoや社内パッケージ:SSH/トークンを安全に使う🧷🔑
「社内ライブラリ(private repo)を npm ci で取ってきたい」「GitHub Packages / private npm レジストリから依存を入れたい」——この瞬間が、秘密が漏れやすい最大の山場です😵💫💥
ここを安全に越えるコツは一言でいうと👇
✅ “ビルドに渡す。でもイメージに残さない” 🔥
そのための主役が BuildKit の SSH マウント / Secret マウントです。(Docker Documentation)
この章でできるようになること🎯✨
- private repo を SSH鍵をイメージへコピーせずにビルドできる🔐
- private npm レジストリのトークンを レイヤ・ログ・履歴に残さずに依存導入できる🫥
- 「どの認証を使うべき?」が選べるようになる(Deploy key / fine-grained PAT / CI用トークン)🧠
- AI拡張を使っていても、秘密を巻き込まない手順が作れる🤖🛡️
1) まず知っておく“事故り方”あるある😇💣
事故パターンA:Dockerfile にトークンを ARG/ENV で入れる
ARG NPM_TOKEN=...とかENVとかで渡すと、ビルド履歴・キャッシュ・ログに残る可能性が出ます⚠️- 「消したつもり」でも
docker historyなどでバレることがあります🫣
事故パターンB:.npmrc や ~/.ssh を COPY してしまう
- これはもう漏れてくださいって言ってるのに近いです😱
- さらに
.dockerignoreが甘いと、ビルドコンテキストに混入して事故率UP📦💥
事故パターンC:URLにトークン埋め込み(例:https://TOKEN@...)
- コマンド履歴・ログ・ツールの表示・AIへの貼り付けで漏れやすいです🧨
2) 正攻法マップ🗺️✨「これを使えば“残らない”】【本命】
本命①:SSHエージェント転送(BuildKitの RUN --mount=type=ssh)🧑🚀🔐
- 秘密鍵ファイルをコンテナに入れない
- “今このビルド中だけ” SSH を使えるようにする方式です。(Docker Documentation)
本命②:BuildKit secret(RUN --mount=type=secret)🤫🧪
- npmトークンや
.npmrcを そのRUN中だけ見せる方式 - レイヤに残りにくい設計になっています。(Docker Documentation)
Compose でもできる(build.ssh)🧩
- Compose 側でも “ビルド中に使うSSH” を指定できます。(Docker Documentation)
3) 認証の選び方(どれが一番安全?)🥇🔍
ここ、迷いがちなので “ざっくり優先順位” を置きます👇
✅ private repo を「読むだけ」なら:Deploy key(読み取り専用)🗝️📖
- リポジトリ単位で鍵を付けられて、用途が限定できるのが強い💪
- 「書き込み許可」は基本オフで運用するのが安心。(GitHub Docs)
✅ “人のトークン”を使うなら:fine-grained PAT(期限短め)⏳🔐
- GitHub は classic PATより fine-grained を推奨していて、リポジトリや権限を絞れます。(GitHub Docs)
✅ CI(GitHub Actions)なら:GITHUB_TOKEN を最小権限で🎛️🛡️
- 最小権限(例:
contents: read)が基本、必要なときだけ増やすのが推奨です。(GitHub Docs)
✅ npm系(private packages)なら:CIは env + .npmrc が公式推奨🧾🔑
- npm公式は、CIでは トークンを環境変数で持たせて
.npmrcで使う流れを説明しています。(npm ドキュメント) - ただし Dockerビルド中は BuildKit secret の方が安全設計に寄せやすいです(後でやります)😎(Docker Documentation)
4) ハンズオン①:private repo を SSHで取ってきてビルドする🧪🐙
ゴール:
npm ciがgit+ssh依存を含んでいても、鍵をイメージに入れずにビルドできる✨
(1) .dockerignore で「そもそも混入させない」🧹
まずは物理ガードです(超大事)🛡️
## secrets
.env
.npmrc
**/.npmrc
.ssh
**/.ssh
## node
node_modules
dist
(2) Dockerfile(SSH が必要な RUN だけに --mount=type=ssh)🔐
例は Node の Active LTS 系(2026年2月時点だと v24 が Active LTS)を使う想定で書きます。(Node.js)
## syntax=docker/dockerfile:1
FROM node:24-alpine AS deps
WORKDIR /app
## git + ssh クライアントだけ入れる
RUN apk add --no-cache git openssh-client
## 先に依存定義だけコピー(キャッシュ効かせる)
COPY package.json package-lock.json ./
## known_hosts を固定(対話プロンプト回避)
## ※安全のため本当は known_hosts を厳密に管理したいが、学習段階ではまず固定の考え方を掴む
RUN mkdir -p -m 700 /root/.ssh && \
ssh-keyscan github.com >> /root/.ssh/known_hosts
## ここが肝:秘密鍵ファイルは入れず、SSHエージェントだけ借りる
RUN --mount=type=ssh npm ci
FROM node:24-alpine AS runner
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["node", "src/index.js"]
# syntax=docker/dockerfile:1は「最新の安定構文を使う」推奨の書き方です。(Docker Documentation)RUN --mount=type=sshは Dockerfile の公式リファレンスにも載っています。(Docker Documentation)
(3) ビルドコマンド(SSHエージェントを渡す)🚀
docker build --ssh default -t myapp:dev .
ポイント:この方式は「エージェントに入ってる鍵」を使うので、先に鍵をエージェントへ登録しておく必要があります🔑➡️🧠 (ここが詰まりポイントになりがち!後でまとめます)
5) ハンズオン②:private npm レジストリのトークンを“残さず”使う🧪🕵️♂️
ゴール:
.npmrcをイメージに残さず、トークンも残さずにnpm ciする✨
(A) BuildKit secret で .npmrc を “そのRUNだけ” 読ませる(おすすめ)🥇
BuildKit は secret を渡して、Dockerfile 側で mount して使う2段構えです。(Docker Documentation)
- 手元に “ビルド専用
.npmrc” を用意(トークン直書きでもOKだが、扱い注意!) 例(スコープやレジストリは自分の環境に合わせてね)👇
@my-scope:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=REPLACE_ME
always-auth=true
- ビルド時に secret として渡す👇
docker build --secret id=npmrc,src=.npmrc.build -t myapp:dev .
- Dockerfile 側は、
npm ciの行だけ secret mount する👇
## syntax=docker/dockerfile:1
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
- これで
.npmrcはその RUN でだけ見える形に寄せられます🤫✨(Docker Documentation)
(B) CI なら env + .npmrc(npm公式の基本パターン)🏭
npm公式は CI/CD で「トークンを環境変数に置き、プロジェクト .npmrc を使う」流れを説明しています。(npm ドキュメント)
また設定は .npmrc にトークンを置くのが一般的で、npm login で作るのが安全寄り、という注意もあります。(npm ドキュメント)
ただし Dockerの “ビルド” 中に env を渡すのはログやレイヤの観点で事故りやすいので、上の (A) を第一候補にするのが無難です😌🛡️
6) Compose でやる場合(build.ssh)🧩🐳
Compose の build には ssh という指定ができます(例:default エージェントをマウント)🧷(docs.docker.jp)
services:
app:
build:
context: .
ssh:
- default
CLI でやるなら👇(こちらも --ssh が用意されています)(matsuand.github.io)
docker compose build --ssh default
7) AI拡張がいる世界の“秘密の守り方”🤖🧱⚠️
AIが便利なほど、うっかりが増えます😇 ここだけはルール化しちゃうのがおすすめ👇
- 秘密っぽい文字列をAIに貼らない(ログもそのまま貼らない)🙅♂️
- エラー貼るときは トークン/URL/Authorization/
.npmrc部分を黒塗り🖍️ - 「AIが提案した
COPY ~/.ssh」みたいな案は、即却下できる判断基準を持つ(=この章の内容)🛡️✨
8) よくある詰まりポイント集(ここで沼る)🪤🧯
❌ Permission denied (publickey) が出る😵
だいたいこれ👇
ssh-agentに鍵が入ってない(ssh-addしてない)- そもそも依存取得が SSH じゃなく HTTPS になってる(
git+httpsなど) - known_hosts 周りで止まっている(対話が発生して build が落ちる)
❌ Compose の ssh: が効かない / “unsupported option” っぽい🤔
- 古い
docker-compose(Python版)系だと対応してないことがあります - 今は
docker compose(Compose v2)側の機能として扱うのが安全寄りです(この章の例はそっち想定)📌
9) 仕上げ:5分セルフ監査✅🔍(漏れてない?)
-
docker history --no-trunc myapp:devを見て、トークンや鍵の痕跡が無い✅ -
リポジトリ内を検索して、トークンっぽい文字列が混入してない✅
-
.dockerignoreに.npmrc/.ssh/.envが入ってる✅ -
使ってるトークンは
- 期限あり
- 権限しぼり(fine-grained PAT / read-only deploy key)
- 用途ラベルあり(どの用途か分かる)✅ (GitHub Docs)
まとめ🎉
この章の結論はシンプルです👇😄
- private repo → SSH agent forwarding(
--mount=type=ssh)が第一候補🔐(Docker Documentation) - private packages → **BuildKit secret で
.npmrcを “その瞬間だけ”**🤫(Docker Documentation) - トークンは 最小権限・期限短め・用途固定(fine-grained / deploy key / CIトークン)🛡️(GitHub Docs)
次の第25章(ローテーション&失効)で、ここで作った仕組みに「漏れた前提の復旧力」を足して完成度を上げられます🚑🔁✨