Dockerfileで.npmrcをセキュアに扱う【Build secrets】
背景
社内で Github Package でプライベート npm パッケージを運用しており、それを npm インストールするためのアクセストークンとして.npmrc
を使用しています。
最近、Dockerfile での.npmrc の取り扱いをセキュア化しました。その過程で少しだけコツと注意がいったので、共有したいと思います。
ちなみに.npmrc とはなんぞ?ということ本記事では省略します。ご存知なければ以下の記事が分かりやすかったのでご参照ください。
ビフォーアフター
Before
ルートディレクトリに.npmrc を配置しておき、pakage.json, package-lock.json と共にコピーして npm ci していました。
ちなみに本題とはそれますが、Dockerfile のベストプラクティスに則り、いきなりCOPY . .
するよりも、この方が Docker のキャッシュが効きやすいため順番を最適化しています。
## -- Build Stage
FROM node:18.20.3 AS build
WORKDIR /usr/src/app
# 本題はここ↓
COPY package*.json .npmrc .
RUN npm ci
COPY . .
RUN npm run build
## -- Production Stage
FROM node:18.20.3
WORKDIR /usr/src/app
...COPY系の処理など
CMD [ "node", "dist/main.js" ]
ただし、これはセキュアな書き方ではないので避けたいです。
理由は、.npmrc はプライベート npm レジストリへのアクセストークンであり秘匿情報なので、Docker イメージに直接コピーするとイメージレイヤに保存されてしまうからです。
そうなると、docker history
コマンドなどにより、過去のレイヤーに含まれる.npmrc ファイルの内容を確認できてしまうことになります。
After
抜粋ですが最終的に Dockerfile はこうなりました。
.npmrc は COPY するのではなく、一時的なシークレット情報として引き渡す形になりました。
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
Docker には Build Secrets
という機能があり、その機能の一部であるSecret mounts
を利用することで、ビルドの間に限り秘匿情報をビルドコンテナ内に引き渡すことができます。
つまり、Docker レイヤに.npmrc の情報を残さない形で利用できます。
【補足】
target
がマウント先のパス・ファイル名になります。
dst
, destination
でも同じ意味で使えます。(参考)
【重要: 注意点】
そのままだと、COPY . .
でローカルに配置している.npmrc が結局 Docker レイヤに乗ってしまいます。
.dockerignore
に追記して COPY されないようにしてください。
dist
node_modules
.npmrc # 追記
ビルド時のシークレットの受け渡し方
上記により、ビルド時に.npmrc をシークレットとしてセキュアに引き渡せるようになりました。
次は、実際のビルド時の secret id の引き渡し方です。
例として、Docker Composeと、Github Actionsで利用する場合をそれぞれ示します。
Docker Compose の場合
以下の手順です。
- ローカルのリポジトリルートに.npmrc ファイルを配置。
- トップレベルの
secrets
ディレクティブでローカルにある.npmrc ファイルの所在地を指定。 - Dockerfile を利用するコンテナの
secrets
にて、上記 secret を指定。 - 環境変数で
DOCKER_BUILDKIT: 1
を指定して、明示的に有効化すると良い(docker バージョンは開発者環境によるため)
docker-compose.yaml(抜粋)
services:
app:
build:
context: .
dockerfile: Dockerfile
secrets:
- npmrc # 3.
environment:
DOCKER_BUILDKIT: 1 # 4.
secrets:
npmrc:
file: .npmrc # 2.
Github Actions の場合 ß
以下の手順です。
- リポジトリシークレットに.npmrc のトークンを
NPM_TOKEN
として保存しておく。 - .npmrc ファイルを動的に生成。
- docker build コマンドの secret オプションにて指定。
src
にて先で作った.npmrc のパスを指定できる。
- name: docker build & push
run: |
echo "engine-strict=true" >> ./.npmrcß
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ./.npmrc
docker --version
docker build --secret id=npmrc,src=.npmrc --pull -t ${GAR_REPO}:${{ env.COMMIT_SHA }} .
docker push ${GAR_REPO}:${{ env.COMMIT_SHA }}
【補足】
GihubActions の ubuntu-latest ランナーでは、デフォルトで Docker がインストールされています。
実際にワークフロー実行して確認したところ、Docker バージョンは 26.1.3 でした。(2024 年 12 月現在)
v23.0 より新しいので、BuildKit はデフォルトで有効になっています。そのためここでは明示的に有効化しませんでした。
一応、ワークフローにdocker --version
を書いておくと良いです。
参考
Discussion