Spring BootアプリをDockerコンテナで動かす方法 - Dockerfile作成からDocker Compose連携まで
Spring Bootアプリをいざコンテナ化しようとすると、「Dockerfileの書き方がよくわからない」「Docker ComposeでPostgreSQLと繋がらない」という壁に当たることが多いです。
この記事では、動く Dockerfile を最短距離で作るところから始め、マルチステージビルドや Layered JAR によるイメージ最適化、Docker Compose での DB 連携、そして本番移行時のセキュリティ考慮まで、一気通貫で解説します。
読了後には次のことができるようになります。
- Spring Boot 向けに最適化した Dockerfile を自分で書ける
- Docker Compose で Spring Boot + PostgreSQL をローカルで一発起動できる
- 本番コンテナ運用で最低限押さえるべきポイントを把握している
なぜSpring BootアプリをDockerコンテナで動かすのか
「自分のPCでは動くのに本番で動かない」という問題はチーム開発の永遠の悩みです。Docker コンテナを使えば、アプリの実行環境をコードとして管理できるため、開発・ステージング・本番で 同一の環境 を再現できます。
主なメリットを整理すると以下のとおりです。
| メリット | 説明 |
|---|---|
| 環境再現性 | OS・JVM・依存ライブラリのバージョンをコンテナに封じ込める |
| チーム開発の均一化 | 全員が同じ Dockerfile を使うため「手元だけ動く」問題がなくなる |
| CI/CD との親和性 | GitHub Actions や Jenkins でイメージをビルド・テスト・デプロイするパイプラインが組みやすい |
| クラウドデプロイの入口 | AWS ECS・Google Cloud Run・Kubernetes など、クラウドサービスの多くはコンテナを前提としている |
本記事でカバーする内容の全体像は次のとおりです。
Dockerfile(最小構成)
↓
マルチステージビルド(サイズ削減)
↓
Layered JAR(ビルド高速化)
↓
Docker Compose(DB 連携・ローカル開発)
↓
本番考慮(セキュリティ・環境変数管理)
最小構成のDockerfileを書く
まずは動く Dockerfile を最短距離で作ります。Spring Boot アプリは mvn package で生成されるファット JAR(target/app.jar)を実行するだけなので、Dockerfile はシンプルです。
ベースイメージの選定
よく使われる選択肢は次の2つです。
| イメージ | 特徴 |
|---|---|
eclipse-temurin:21-jre-alpine | Eclipse Adoptium 提供。OpenJDK 互換でデファクトスタンダード |
amazoncorretto:21 | AWS が提供する OpenJDK ディストリビューション。AWS 環境との親和性が高い |
ランタイムステージではビルドツールが不要なため、JDK ではなく JRE を選ぶことでイメージサイズを抑えられます(後述のマルチステージビルドで詳しく解説します)。
最小構成のDockerfile
# ベースイメージ(JRE のみ・JDK 不要)
FROM eclipse-temurin:21-jre-alpine
# 作業ディレクトリの設定
WORKDIR /app
# ビルド済み JAR をコンテナにコピー
COPY target/app.jar app.jar
# 8080 ポートを公開(ドキュメント的な意味合い。-p オプションで実際にマッピングする)
EXPOSE 8080
# アプリ起動コマンド
ENTRYPOINT ["java", "-jar", "app.jar"]
各命令の意味を確認しておきましょう。
WORKDIR: 以降の命令の作業ディレクトリを設定します。存在しない場合は自動作成されます。COPY: ホストのファイルをコンテナ内にコピーします。EXPOSE: コンテナが使用するポートをドキュメントとして宣言します。実際のポートマッピングはdocker run -pで行います。ENTRYPOINT: コンテナ起動時に実行するコマンドを配列形式(exec 形式)で指定します。CMDとの違いは、ENTRYPOINTはdocker runの引数で上書きされず、CMDは上書き可能な点です。
ビルドと起動の確認
# JAR をビルド
./mvnw package -DskipTests
# Docker イメージをビルド
docker build -t myapp:latest .
# コンテナを起動(ホストの 8080 をコンテナの 8080 にマッピング)
docker run -p 8080:8080 myapp:latest
http://localhost:8080 にアクセスしてアプリが返ってくれば成功です。
マルチステージビルドでイメージサイズを削減する
上記の最小構成 Dockerfile には1つ問題があります。COPY target/app.jar を実行するためには、Dockerfile を実行する前にホスト側で mvn package を実行しておく必要がある 点です。CI/CD 環境では Maven や JDK のインストールが別途必要になります。
マルチステージビルドを使えば、Dockerfile の中でビルドから実行まで完結できます。
シングルステージの問題点
Maven と JDK を含んだイメージをそのまま本番イメージにすると、イメージサイズが大きくなります。
| 構成 | イメージサイズの目安 |
|---|---|
maven:3.9-eclipse-temurin-21(JDK + Maven) | 約 500 MB〜 |
eclipse-temurin:21-jre-alpine(JRE のみ) | 約 80 MB〜 |
本番環境に Maven や JDK コンパイラは不要です。マルチステージビルドで ビルドステージとランタイムステージを分離 します。
マルチステージビルドのDockerfile
# ===== Stage 1: ビルドステージ =====
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
# 依存関係の解決(キャッシュ最適化のため pom.xml を先にコピー)
COPY pom.xml .
RUN mvn dependency:go-offline -B
# ソースコードをコピーしてビルド
COPY src ./src
RUN mvn package -DskipTests -B
# ===== Stage 2: ランタイムステージ =====
FROM eclipse-temurin:21-jre-alpine AS runtime
WORKDIR /app
# ビルドステージで生成した JAR だけをコピー
COPY --from=build /workspace/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
COPY --from=build がポイントです。ビルドステージの成果物(JAR)だけをランタイムイメージにコピーし、Maven・JDK・ソースコードは最終イメージには含まれません。
# マルチステージビルドで one-command ビルド&イメージ作成
docker build -t myapp:latest .
レイヤーキャッシュを活かすCOPY順序の最適化
Docker はイメージを レイヤー という単位で管理し、変更のないレイヤーはキャッシュを再利用します。このキャッシュの仕組みを理解すると、ビルド時間を大幅に短縮できます。
レイヤーキャッシュの仕組み
FROM maven:3.9-eclipse-temurin-21 ← レイヤー1(ほぼ変わらない)
WORKDIR /workspace ← レイヤー2(ほぼ変わらない)
COPY pom.xml . ← レイヤー3(依存追加時のみ変わる)
RUN mvn dependency:go-offline ← レイヤー4(レイヤー3が変わった時のみ再実行)
COPY src ./src ← レイヤー5(コード変更のたびに変わる)
RUN mvn package ← レイヤー6(レイヤー5が変わった時のみ再実行)
ポイントは「 変更頻度の低いものを上(前)に置く 」ことです。
悪い例(キャッシュが効かない)
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
# ソースコードを先にコピーするとコード変更のたびに依存解決が再実行される
COPY . .
RUN mvn package -DskipTests -B
この書き方では、src/ 以下の1ファイルでも変更するたびに mvn package(依存ライブラリのダウンロード含む)が最初から実行されます。
良い例(キャッシュが効く)
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
# pom.xml だけを先にコピーして依存関係を解決
COPY pom.xml .
RUN mvn dependency:go-offline -B
# 依存関係が変わっていなければここのレイヤーはキャッシュされる
COPY src ./src
RUN mvn package -DskipTests -B
pom.xml が変わらない限り mvn dependency:go-offline はキャッシュから再利用されます。日常的なコード変更では 大幅なビルド時間短縮 が期待できます。
なお、dependency:go-offline はプロジェクト依存ライブラリの JAR をキャッシュしますが、Mavenプラグイン自体(spring-boot-maven-plugin 等)はキャッシュ対象外です。CI 環境でネットワーク制限がある場合は mvn -B dependency:go-offline dependency:resolve-plugins の使用も検討してください。
Spring Boot 3.x の Layered JAR を使った高度なDockerfile
Spring Boot 2.3 以降では Layered JAR という仕組みが導入されました。これを使うと、ファット JAR の中身をレイヤーごとに分割してコンテナにコピーできるため、依存ライブラリのキャッシュがさらに効きやすくなります。
Layered JARの構造
Spring Boot の Layered JAR は次の4レイヤーに分割されます。
| レイヤー | 内容 | 変更頻度 |
|---|---|---|
dependencies | サードパーティの依存ライブラリ | 低い |
spring-boot-loader | Spring Boot ローダー | 非常に低い |
snapshot-dependencies | SNAPSHOT 版の依存ライブラリ | 中程度 |
application | アプリのクラスファイル・リソース | 高い |
変更頻度が高い application レイヤーだけが差し替わるため、毎回のデプロイで 依存ライブラリのレイヤーキャッシュが再利用 されます。
pom.xml での Layered JAR 有効化
Spring Boot 3.x ではデフォルトで有効ですが、明示的に設定するには次のようにします。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- Layered JAR を明示的に有効化(Spring Boot 3.x ではデフォルト true) -->
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>
Layered JAR を使ったDockerfile
# ===== Stage 1: ビルドステージ =====
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# Layered JAR を展開して各レイヤーを抽出
RUN java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted
# ===== Stage 2: ランタイムステージ =====
FROM eclipse-temurin:21-jre-alpine AS runtime
WORKDIR /app
# 変更頻度の低いレイヤーから順にコピー(キャッシュ最大化)
COPY --from=build /workspace/target/extracted/dependencies/ ./
COPY --from=build /workspace/target/extracted/spring-boot-loader/ ./
COPY --from=build /workspace/target/extracted/snapshot-dependencies/ ./
COPY --from=build /workspace/target/extracted/application/ ./
EXPOSE 8080
# Spring Boot 3.2以降。3.1以前は org.springframework.boot.loader.JarLauncher を使用
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
通常のマルチステージビルドとの違いは、アプリのコードだけを変更した場合に dependencies レイヤーのキャッシュが再利用される点です。依存ライブラリのダウンロードが不要なため、CI 環境での繰り返しビルドが高速化されます。
注意:
JarLauncherのパッケージ名は Spring Boot 3.2 以降org.springframework.boot.loader.launch.JarLauncherに変更されました。Spring Boot 3.1 以前ではorg.springframework.boot.loader.JarLauncherを使用してください。また、Spring Boot 3.3以降ではjava -Djarmode=tools extract --destination target/extractedの使用が推奨されています。layertoolsモードは引き続き動作しますが、将来的に削除される可能性があります。
Docker ComposeでSpring Boot + PostgreSQLを連携起動する
ローカル開発でアプリとDBを別々に起動するのは手間がかかります。Docker Compose を使えば、1コマンドで複数サービスをまとめて起動 できます。
docker-compose.yml の全体像
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp_db
POSTGRES_USER: myapp_user
POSTGRES_PASSWORD: myapp_password
ports:
- "5432:5432"
volumes:
# コンテナ削除後もデータを保持する名前付きボリューム
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp_user -d myapp_db"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/myapp_db
SPRING_DATASOURCE_USERNAME: myapp_user
SPRING_DATASOURCE_PASSWORD: myapp_password
SPRING_JPA_HIBERNATE_DDL_AUTO: update
depends_on:
db:
condition: service_healthy
restart: on-failure # 本番では unless-stopped も検討
volumes:
postgres_data:
depends_on だけでは不十分な理由
depends_on は PostgreSQL コンテナが起動した時点 で次のサービスを開始しますが、PostgreSQL が 接続を受け付けられる状態 になるまでには数秒かかります。その間に Spring Boot が起動すると DB 接続エラーになります。
healthcheck と condition: service_healthy を組み合わせることで、PostgreSQL が実際にクエリを受け付けられるようになってから Spring Boot が起動するよう 確実に制御 できます。
起動と確認
# バックグラウンドで起動
docker compose up -d
# ログをリアルタイム確認
docker compose logs -f app
# 停止(コンテナ削除・ボリュームは保持)
docker compose down
# ボリュームも含めて完全削除
docker compose down -v
Docker Compose のサービス名(db)はコンテナ間通信のホスト名として使われます。そのため、Spring Boot 側の接続 URL は localhost ではなく jdbc:postgresql://db:5432/myapp_db と記述します。
application.propertiesの環境変数外出しとプロファイル連携
コンテナ環境では DB 接続情報などをコードにハードコードせず、環境変数で注入する のがベストプラクティスです。
環境変数参照の記述方法
# application.properties
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/myapp_db}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:myapp_user}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:myapp_password}
spring.jpa.hibernate.ddl-auto=${SPRING_JPA_HIBERNATE_DDL_AUTO:update}
# spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # Spring Boot 3.x + Hibernate 6では自動検出のため通常不要
${変数名:デフォルト値} の形式を使うと、環境変数が設定されていない場合のフォールバック値を指定できます。ローカル直接起動時はデフォルト値が使われ、Docker Compose 経由ではコンテナの環境変数が優先されます。
Spring Boot の設定管理全般については Spring Bootのapplication.propertiesを使いこなす設定管理ガイド も参照してください。
.env ファイルへの機密情報の分離
Docker Compose は同じディレクトリに .env ファイルがあれば自動で読み込みます。機密情報を .env に分離することで、docker-compose.yml を Git にコミットしやすくなります。
# .env ファイル(必ず .gitignore に追加すること)
POSTGRES_PASSWORD=myapp_password
SPRING_DATASOURCE_PASSWORD=myapp_password
# docker-compose.yml での参照
services:
db:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
app:
environment:
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
プロファイルとの組み合わせ
Docker 環境専用の設定ファイル application-docker.properties を作成し、Docker Compose から SPRING_PROFILES_ACTIVE=docker を渡すことで、 環境ごとに設定を切り替える ことができます。
# docker-compose.yml
services:
app:
environment:
SPRING_PROFILES_ACTIVE: docker
プロファイルを使った環境設定の切り替えについては Spring BootのProfileを使って環境によって違う設定を安全に切り替える方法 で詳しく解説しています。
ローカル開発時のホットリロードに対応する(Spring Boot DevTools)
コンテナ内でコードを変更するたびに docker build をやり直すのは非効率です。Spring Boot DevTools と Docker Compose のボリュームマウントを組み合わせると、コンテナ内でもある程度のホットリロードが実現できます。
ただし、完全な対応は設定が複雑になるため、ここでは設計の考え方のみ紹介します。
docker-compose.override.yml での分離
ホットリロード設定はローカル専用なので、docker-compose.override.yml に分離するのが定石です。docker compose up 実行時に docker-compose.yml と docker-compose.override.yml が 自動的にマージ されます。
# docker-compose.override.yml(ローカル開発専用)
services:
app:
environment:
SPRING_DEVTOOLS_RESTART_ENABLED: "true"
実際のボリュームマウントパスはビルド構成(通常ビルド・Layered JAR 展開後など)に依存するため、詳細は Spring Boot DevTools 公式ドキュメント を参照してください。
本番 CI/CD 環境では docker-compose.override.yml を使わないか、明示的に docker compose -f docker-compose.yml up と指定することで override を無効化できます。
本番移行時のセキュリティ考慮事項
ローカルで動かせるようになったら、本番環境への移行前に以下のポイントを確認しましょう。
非rootユーザーでJVMを実行する
デフォルトでは Docker コンテナ内のプロセスは root で実行されます。コンテナがセキュリティ上の問題で侵害された場合のリスクを低減するため、 専用ユーザーを作成して切り替える ことを推奨します。
FROM eclipse-temurin:21-jre-alpine AS runtime
WORKDIR /app
# 専用のシステムユーザーとグループを作成(Alpine Linux の場合)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# --chown オプションでコピーと同時に所有者を設定(レイヤー削減)
COPY --chown=appuser:appgroup --from=build /workspace/target/*.jar app.jar
# 非 root ユーザーに切り替え
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
シークレット管理の方針
環境変数に機密情報を直接書くのは手軽ですが、docker inspect でコンテナの環境変数が見えてしまうリスクがあります。本番環境では以下を検討してください。
| 方法 | 適した場面 |
|---|---|
| Docker Secrets | Docker Swarm 環境 |
| HashiCorp Vault | 大規模・高セキュリティ要件の環境 |
| AWS Secrets Manager / GCP Secret Manager | 各クラウドプロバイダーを使用している場合 |
| Kubernetes Secrets | Kubernetes 環境 |
ヘルスチェックと Actuator の連携
Spring Boot Actuator のヘルスエンドポイントを Docker Compose の healthcheck と連携させることで、アプリケーションが実際に 正常起動した状態 を検出できます。
# docker-compose.yml
services:
app:
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/actuator/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Actuator のセットアップについては Spring Boot Actuatorで始めるアプリケーション監視入門 を参照してください。
イメージの脆弱性スキャン
本番デプロイ前にイメージの脆弱性スキャンを習慣づけましょう。
# Docker Scout(Docker Desktop に統合)
docker scout cves myapp:latest
# Trivy(OSS の脆弱性スキャナー)
trivy image myapp:latest
不要なポートを公開しない
EXPOSE はドキュメント的な意味合いが強いですが、Docker Compose や docker run での -p マッピングで 本当に必要なポートのみ をホストに公開するよう意識してください。Actuator のポートをプロダクション環境で外部に公開しないのも重要です。
よくあるエラーと対処法
DB接続エラー(Connection refused)
症状: Spring Boot 起動時に Connection refused や HikariPool ... connection is not available が出る。
原因と対処: depends_on だけでは PostgreSQL が接続受け付け可能になるまで待てません。前述の healthcheck + condition: service_healthy を設定してください。
depends_on:
db:
condition: service_healthy # これが重要
Port already in use
症状: docker compose up 時に Bind for 0.0.0.0:8080 failed: port is already allocated。
対処: ホスト側でポートを使用しているプロセスを確認します。
# 使用中のポートを確認(macOS / Linux)
lsof -i :8080
# または前回の compose を停止してから再起動
docker compose down
docker compose up -d
ClassNotFoundException(マルチステージCOPYパスミス)
症状: イメージビルドは成功するが、起動時に ClassNotFoundException や no main manifest attribute が出る。
原因: マルチステージビルドで COPY --from=build のパスが間違っている可能性があります。ビルドステージに一時的に入って JAR の場所を確認しましょう。
# ビルドステージで生成された JAR の場所を確認
docker build --target build -t myapp-build .
docker run --rm myapp-build ls /workspace/target/
環境変数が反映されない
症状: docker-compose.yml の environment を変更したのに設定が変わらない。
対処: docker compose up ではイメージが再ビルドされません。 --build フラグ を付けて実行してください。
docker compose up -d --build
M1/M2 Mac(ARM)でのプラットフォーム差異
症状: WARNING: The requested image's platform (linux/amd64) does not match the detected host platform、または本番(x86)環境でイメージが動かない。
対処: --platform オプションでターゲットプラットフォームを明示します。
# linux/amd64 向けにビルド(本番が x86_64 の場合)
docker build --platform linux/amd64 -t myapp:latest .
docker-compose.yml でも指定できます。
services:
app:
platform: linux/amd64
build:
context: .
まとめ
この記事では Spring Boot アプリの Docker コンテナ化を段階的に解説しました。
| ステップ | ポイント |
|---|---|
| 最小構成 Dockerfile | JRE ベースイメージ・EXPOSE・ENTRYPOINT の役割を理解する |
| マルチステージビルド | ビルドツールを本番イメージに含めず、サイズを大幅削減 |
| レイヤーキャッシュ最適化 | pom.xml を先にコピーして依存解決のキャッシュを活かす |
| Layered JAR | 依存レイヤーを分離してビルド高速化(Spring Boot 3.x 推奨) |
| Docker Compose | healthcheck + condition: service_healthy で確実な DB 待ち |
| 環境変数管理 | .env ファイルと ${変数名} 記法で機密情報を分離 |
| 本番セキュリティ | 非 root ユーザー実行・シークレット管理・脆弱性スキャンを習慣化 |
コンテナ化はクラウドデプロイや CI/CD パイプラインへの入口でもあります。まずはローカルで Docker Compose 環境を動かし、CI でのイメージビルドへとステップアップしていきましょう。
よくある質問
Spring BootのDockerfileにはJDKとJREどちらのベースイメージを使うべきですか?
ランタイムステージには JRE を使うべきです。JDK にはコンパイラ・デバッグツールなどビルド時にのみ必要なツールが含まれており、本番イメージを不必要に大きくします。マルチステージビルドを使い、ビルドステージで JDK(maven:3.9-eclipse-temurin-21)を使い、ランタイムステージで JRE(eclipse-temurin:21-jre-alpine)に切り替えるのが推奨パターンです。
マルチステージビルドを使わないと何が問題になりますか?
主に2つの問題があります。①イメージサイズが大きくなる(Maven・JDK を含むと 500 MB 以上になることも)、②ソースコードがイメージ内に残るセキュリティリスクです。マルチステージビルドを使えばランタイムイメージには JAR ファイルだけが含まれるため、イメージサイズの削減とセキュリティ向上が同時に実現できます。
Docker ComposeでSpring BootがPostgreSQLに接続できない原因は何ですか?
最も多い原因は depends_on のみで healthcheck を設定していないことです。depends_on は PostgreSQL コンテナの起動 を待つだけで、PostgreSQL が 接続を受け付けられる状態 になるまでは待ちません。healthcheck と condition: service_healthy を組み合わせることで確実に解決できます。
Layered JARと通常のfat JARではDockerビルド時間にどれくらい差がありますか?
依存ライブラリの変更がない通常のコード変更の場合、Layered JAR を使うと 依存ライブラリのレイヤーがキャッシュから再利用 されるため、プッシュ・プル時のデータ転送量が大幅に削減されます。ライブラリが数十〜数百 MB あるプロジェクトでは、CI/CD での繰り返しデプロイ時間の短縮効果が特に大きくなります。
application.propertiesの設定値をDocker Composeから上書きするにはどうすればよいですか?
Spring Boot は環境変数でプロパティを上書きできます。spring.datasource.url の場合、環境変数名を SPRING_DATASOURCE_URL(ドット→アンダースコア、大文字)にすると自動的に上書きされます。application.properties 側で ${SPRING_DATASOURCE_URL:デフォルト値} と書いておくと、環境変数がない場合のフォールバックも設定できます。
本番環境でDockerコンテナを非rootユーザーで実行するにはどう設定しますか?
Dockerfile に RUN addgroup -S appgroup && adduser -S appuser -G appgroup でユーザーを作成し、 USER appuser で切り替えます。Alpine Linux ベースイメージの場合は addgroup/adduser コマンドを使います。Debian/Ubuntu ベースの場合は groupadd/useradd コマンドになります。
M1/M2 MacでSpring BootのDockerイメージをビルドする際の注意点は何ですか?
M1/M2 Mac は ARM アーキテクチャのため、本番環境(多くは x86_64)向けにビルドする場合は --platform linux/amd64 オプションが必要です。docker build --platform linux/amd64 -t myapp:latest . と指定します。Docker Desktop の設定で Rosetta エミュレーションを有効にすることで、パフォーマンスを維持しつつクロスプラットフォームビルドが可能になります。