前回の記事では、複数のコンテナを操る docker-compose.yml の書き方を紹介した。今回は、そのコンテナの中身を定義する Dockerfile について書く。
今回作成したバックエンド(Java/Spring Boot)とフロントエンド(React/Node.js)のDockerfileには、「マルチステージビルド」と使っている。
これを使うと、「イメージサイズが小さくなり、セキュリティも向上する」
バックエンド編:Java (Spring Boot)
Javaアプリを動かすには、ビルドツールの「Maven」や「Gradle」が必要だが、実行時にこれらは不要となる。そこで、ビルドする段階と、実行する段階を分ける。
# --- Stage 1: ビルド環境 ---
FROM maven:3.9-eclipse-temurin-25 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
# テストはCIでやる前提でスキップし、ビルド時間を短縮
RUN mvn clean package -DskipTests
# --- Stage 2: 実行環境 ---
FROM eclipse-temurin:25-jre
WORKDIR /app
# ビルド環境(Stage 1)で作った成果物(jar)だけをコピー
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]- AS build と COPY –from=build: 最初の FROM で「build」という名前をつけ、そこで mvn package して jarファイルを作る。2つ目の FROM が本番用イメージ。ここには Maven もソースコードも含まれない。あるのは 「JRE(Java実行環境)」と「jarファイル」だけ。 これにより、数百MB単位で容量を節約できる。
- eclipse-temurin:25-jre: JDK(開発キット)ではなく、JRE(実行環境)を選んでいる。コンパイラなどが含まれないため、攻撃者に悪用されるリスクが減る。
フロントエンド編:Node.js (React/Vueなど)
フロントエンド開発では npm run dev サーバーを使うが、本番環境でNode.jsサーバーをそのまま動かすのはパフォーマンス的に最適ではない。 ビルドした静的ファイル(HTML/CSS/JS)を、高速なWebサーバーである Nginx に配信させる。
# --- Stage 1: ビルド環境 ---
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
# npm install より高速で、lockファイルに忠実な ci を使用
RUN npm ci
COPY . .
RUN npm run build
# --- Stage 2: 配信環境 (Nginx) ---
FROM nginx:alpine
# ビルド成果物(dist)をNginxの公開ディレクトリに配置
COPY --from=build /app/dist /usr/share/nginx/html
# SPA用の設定ファイルをコピー
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]- Node.js はビルド時のみ使用: Reactなどのコードはブラウザで動くJavaScriptに変換(トランスパイル)する必要がある。これには Node.js が必要だが、変換が終われば Node.js 自体は不要となる。 最終的なイメージは nginx:alpine ベースになるため、サイズはわずか数十MBに収まる。
- npm ci の採用: npm install ではなく npm ci を使っている。これは package-lock.json を厳密に守ってインストールを行うコマンドで、CI/CDやDockerビルドでの再現性を保証する。
- カスタムNginx設定: ReactのようなSPA(シングルページアプリケーション)では、どのURLにアクセスしても index.html を返す設定が必要になるため、デフォルト設定を上書きしている。
まとめ
今回作成した2つのDockerfileに共通するのは、「作る道具(Maven/Node)」と「動かす場所(JRE/Nginx)」を明確に分けている点となる。