やりたかったこと
Node.jsのアプリをDockerコンテナで動かそうとした。最初は docker run node:18 でコンテナを起動してアプリを手動で入れていたが、毎回同じ手順を繰り返すのが面倒になってきた。Dockerfileを書けば同じ環境を一発で再現できると聞いて試してみた。
環境
- OS: Ubuntu 22.04 LTS
- Docker: 24.0.5
- Node.js: 22系(alpine)
試したこと・うまくいかなかったこと
最初、こんな書き方をした。
FROM node:22
COPY . .
RUN npm install
CMD ["node", "server.js"]
ビルドは通ったが、毎回 npm install のキャッシュが効かずに全パッケージをダウンロードし直していた。2分近くかかっていて、コードを1行直すたびに待ち続けた。
次に node_modules もまるごとCOPYしてビルドしていたが、ホストとコンテナでCPUアーキテクチャが違うせいでバイナリが壊れて起動エラーになった。
Error: /app/node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node: invalid ELF header
.dockerignore を知らなかったのが原因だった。
解決策
package*.json だけを先にCOPYして npm ci を実行する。こうするとpackage.jsonが変わらない限りこのレイヤーのキャッシュが使い回されてビルドが速くなった。
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "server.js"]
.dockerignore も必ず作る。これがないとホスト側の node_modules がコンテナにコピーされてイメージが数GB単位で膨れ上がる。
node_modules
dist
.env
.git
*.log
ビルドして起動するコマンド:
docker build -t myapp .
docker run -d -p 3000:3000 myapp
これで起動した。
ハマったポイント
COPY package*.json ./をCOPY . .より先に書かないとキャッシュが全く効かない。ちょっとしたコード修正のたびにnpm ciが走って3分待ちになった.dockerignoreなしでビルドするとnode_modules(700MB超)がイメージに入る。docker imagesで確認したら1.8GBになっていて驚いたRUN npm installよりRUN npm ciのほうがCI/CDでは安定する。package-lock.jsonがないとnpm ciはエラーになるのでlockファイルは必ずコミットしておくWORKDIR /appを省略すると/直下にファイルが散らばってあとで管理しにくくなったCMDとENTRYPOINTの違いで1時間ハマった。CMDは上書き可能、ENTRYPOINTは固定と覚えた
関連記事
おすすめのVPS
VPSを使って本番環境を構築するなら、以下のサービスがおすすめです。