はじめに
dockerで構築しているNext.js
のフロントエンドアプリケーションのimageをAmazon ECR
にpushしようとしたときに、pushのあまりの遅さにびっくりしたのがことの発端です。
なんとimageサイズが2.54GBになっており、、、そら時間かかるわ状態😵💫
これまでdockerコンテナイメージの軽量化(ここでは減量と呼びます)はあまり意識することはなかったのですが、これを機会に勉強してみようと思い備忘がてら残します。
(体の減量は経験がありますが、dockerイメージの減量に関しては素人なのでコメント等で間違っている部分はご指摘いただけますと幸いです)
dockerイメージが肥大化すると・・・?
いろいろと調べてみると、dockerイメージが肥大化するといろいろと都合が良くないことしかありません。。。
- ローカルストレージを圧迫する
- Docker HubやECRにpush/pullする際に時間がかかる
- CI/CDなどのビルドに時間がかかる
つまり、減量するに越したことはない!
結論
まずは今回行ったことの結論から。
node
のベースイメージを使用したdockerイメージサイズが2.54GB
→ 574MB
まで減量成功!
前提
Next.js
アプリケーションのソースである/frontend
のディレクトリ構成は以下の通り。
├── .dockerignore
├── .eslintrc.json
├── .gitignore
├── .next
│ ├── build-manifest.json
│ ├── cache
│ │ ├── config.json
│ │ └── webpack
│ ├── package.json
・・・
├── Dockerfile
├── Dockerfile.dev
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
・・・
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
Before
何も軽量化を考えていない、脳死Dockerfile(beforeの状態)がこちら。
ただコマンドを羅列しただけのまさに、見るに耐えない状態です😣
# Use the official Next.js base image
FROM node:18.16.0-alpine3.17
# Set the working directory
WORKDIR /usr/src/app
# Copy package.json and yarn.lock to the working directory
COPY /app/package.json /app/yarn.lock ./
# Install dependencies
RUN yarn install
# Copy the rest of the application code
COPY . .
# Build the Next.js application
RUN yarn build
EXPOSE 3000
# Set the command to start the Next.js server
CMD ["yarn", "start", "-H", "0.0.0.0"]
After
ダイエットしたDockerfile(afterの状態)と追加した.dockerignore
がこちら。
# ---- Base Node ----
FROM node:18.16.0-alpine3.17 AS base
WORKDIR /usr/src/app
# Copy package.json and yarn.lock for utilising Docker cache
COPY package.json yarn.lock ./
# ---- Dependencies ----
FROM base AS dependencies
RUN yarn install --production && yarn cache clean
# ---- Copy Files/Build ----
FROM dependencies AS build
WORKDIR /usr/src/app
COPY . /usr/src/app
RUN yarn build
# --- Release ----
FROM base AS release
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
COPY --from=build /usr/src/app/.next ./.next
COPY ./public ./public
EXPOSE 3000
CMD ["yarn", "start", "-H", "0.0.0.0"]
# add git-ignore syntax here of things you don't want copied into docker image
.dockerignore
.git
.gitignore
node_modules
npm-debug.log
README.md
.next
*Dockerfile*
.env
.DS_Store
やったこと
.dockerignore ファイルの使用 / 不要なファイルの削除
.dockerignore
ファイルを使用して、ビルドコンテキストから不要なファイルを除外
これにより、ビルド時間を短縮し、イメージサイズを削減
Dockerイメージはビルドしたアプリケーションに必要なものだけを含むべきです。不要なファイルや依存関係を削除します。
関連のないファイルは少しでもビルドコンテキストに含めないほうが良いですね。
マルチステージビルドの使用
正直、これが一番大きいと思います。
Dockerfile
にマルチステージビルドを導入すると、イメージのサイズを大幅に削減できます。
buildステージでは全ての依存関係とビルドツールをインストールし、アプリケーションをビルドします。
COPY 命令の--from=build
で参照していることがわかります。これにより、buildステージの成果物である app を、releaseステージにコピーすることを可能にし、必要なファイルのみをイメージ内に含めることができるというわけです.
これにより、イメージのサイズを小さく保つことができます。
キャッシュの最適化
Dockerは各命令を実行し、結果を新しいレイヤーとして保存します。
Dockerfileの命令が変更されると、その行とそれ以降の全ての行が再実行されます。可能な限り変更が少ない命令をDockerfileの上部に配置すると、ビルド時間を短縮できます。
具体的には、RUN
やCOPY
などの命令単位でレイヤーが作成されます。
&&
や\
を使いコマンドを並列することで、レイヤー数を減らすことが可能です。
yarn cache clean
も効果的です。
詳しい説明は以下
Yarnはパッケージマネージャーとして機能し、プロジェクトで使用される依存関係を管理します。パッケージをインストールする際、Yarnはインストールしたパッケージのバージョンをグローバルキャッシュに保存します。これにより、将来同じパッケージを再度インストールする必要がある場合、既にダウンロードされキャッシュされているものを再利用することで、インストールの速度を向上させることができます。
ただし、このキャッシュは繰り返しパッケージをインストールすると徐々に大きくなり、ディスクスペースを占有します。yarn cache cleanコマンドは、このキャッシュを削除してディスクスペースを解放するために使用されます。特にDockerイメージをビルドする際には、不要なファイルを削除してイメージのサイズを最小限に抑えることが重要です。そのため、パッケージのインストールが完了したらキャッシュをクリアするのが一般的な実践です。
さいごに
今回は、コンテナイメージ軽量化の手法の中でも限られた手法しかしませんでしたが、突き詰めるとまだまだ軽量化は可能だと思っています。
さらに参考となりそうな手法を参考欄に載せて終わりとしたいと思います。
参考
docker-slim
今回は使用しませんでしたが、こんなのもあるみたいです。
ImageLayers
docker imageのレイヤーは、docker historyコマンドでも確認できる
$ docker images
$ docker history REPOSITORY:TAG
$ 例: docker history mysql:8.0