Docker コンテナ化デプロイ
Dockerはデプロイの革命です。一度ビルドすればどこでも実行でき, 「私の環境では動く」という言い訳に別れを告げましょう。
1. 学ぶこと
- マルチステージDockerfileビルド:ビルダーイメージ + ランタイムイメージ (Distroless / Eclipse Temurin)
.dockerignore最適化とイメージレイヤーキャッシュ戦略- Docker Compose編成:OrderFlow, MySQL, Redisの3サービス連携
- 環境変数注入と設定の外部化
- Bobは150MB未満の本番グレードOrderFlow Dockerイメージを構築しました
2. 運用エンジニアの実話
(1) ペインポイント: 環境の不一致
BobがローカルでOrderFlowのデバッグを終えて本番にデプロイすると, MySQLドライバーのバージョン不一致, JDKバージョンの違い, タイムゾーン設定の差異によりサービスが起動しませんでした。毎回のデプロイで環境の差異のトラブルシューティングに半日かかり, 「私の環境では動く」がチームの口癖になりました。さらに悪いことに, 3台のサーバーにそれぞれ手動デプロイが必要で, 設定の見落れが頻発しました。
(2) Dockerソリューション
Dockerはアプリケーションとすべての依存関係を1つのイメージにパッケージ化します:
FROM eclipse-temurin:17-jre-alpine
COPY target/orderflow-service.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
1つのコマンドでデプロイし, 完全に一致した環境を保証します。
(3) 成果
BobがDockerを使い始めてから, イメージはローカルで一度ビルドし, 3台すべてのサーバーが同じソースから取得して実行するため, 環境の不一致が解消されました。1つのDocker Composeコマンドで完全な環境 (OrderFlow, MySQL, Redis)が起動し, 新入社員も5分でローカル実行できるようになりました。
3. マルチステージDockerfile
(1) マルチステージビルドプロセス
graph TD
A["ステージ1: Builder<br/>Maven + JDK 17<br/>コンパイル + パッケージ"] --> B["orderflow-service.jar"]
B --> C["ステージ2: Runtime<br/>JRE 17 Alpine<br/>JARのみコピー"]
C --> D["最終イメージ<br/>~150MB"]
(1) ▶ サンプル:マルチステージDockerfile
# ステージ1: ビルド
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# 依存関係キャッシュのため先にpom.xmlをコピー
COPY pom.xml .
RUN mvn dependency:go-offline -B
# ソースをコピーしてビルド
COPY src ./src
RUN mvn package -DskipTests -B
# ステージ2: ランタイム
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 非rootユーザーを作成
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# ビルダーからJARをコピー
COPY --from=builder /build/target/*.jar app.jar
# 所有権を設定
RUN chown -R appuser:appgroup /app
USER appuser
# ポートを公開
EXPOSE 8080
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
# アプリケーションを実行
ENTRYPOINT ["java", \
"-XX:+UseG1GC", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
出力:
// 実行成功
| ベースイメージ | サイズ | セキュリティ | ユースケース |
|---|---|---|---|
eclipse-temurin:17 |
~450MB | 低 | 開発/テスト |
eclipse-temurin:17-jre-alpine |
~170MB | 中高 | 本番推奨 |
gcr.io/distroless/java17-debian12 |
~100MB | 最高 | 究極のセキュリティ |
(2) ▶ サンプル:Distrolessイメージ
# ステージ1: ビルド
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# ステージ2: Distrolessランタイム
FROM gcr.io/distroless/java17-debian12
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
出力:
// 実行成功
docker exec -itでデバッグモードに入ることができません。本番環境では推奨されますが, デバッグにはAlpineイメージを使用してください。
4. .dockerignoreとイメージ最適化
(1) ▶ サンプル:.dockerignore
# .dockerignore
.git
.github
.idea
*.md
target/
!target/*.jar
node_modules/
*.log
.env
docker-compose*.yml
Dockerfile*
| 最適化戦略 | 効果 | 説明 |
|---|---|---|
.dockerignore 不要ファイルの除外 |
ビルドコンテキストの削減 | .git, .ideaなどを送信しない |
| 先にpom.xmlをCOPY | 依存関係をキャッシュ | 依存関係が変更されていなければダウンロードをスキップ |
| マルチステージビルド | 最終イメージサイズの削減 | Mavenやソースコードを含まない |
| JDKではなくJRE | ~200MB節約 | ランタイムにコンパイルツールは不要 |
| Alpineベースイメージ | ~280MB削減 | musl libcを使用する最小Linuxシステム |
5. Docker Compose マルチサービス編成
(1) ▶ サンプル:OrderFlow + MySQL + Redis
# docker-compose.yml
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 5
restart: unless-stopped
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
volumes:
mysql-data:
redis-data:
出力:
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
(2) ▶ サンプル:起動と検証
# 全サービスをビルドして起動
docker compose up -d --build
# サービスステータスを確認
docker compose ps
# アプリケーションログを表示
docker compose logs -f app
# APIをテスト
curl http://localhost:8080/actuator/health
# 全サービスを停止
docker compose down -v
出力:
CONTAINER ID IMAGE STATUS
abc123 latest Up 2 hours
| コマンド | 説明 |
|---|---|
docker compose up -d |
バックグラウンドで起動 |
docker compose down |
停止してコンテナを削除 |
docker compose logs -f app |
アプリログを表示 |
docker compose ps |
サービスステータスを表示 |
docker compose build |
イメージのみビルド |
6. 環境変数注入
(1) ▶ サンプル:設定の外部化
# application-prod.yml (Dockerイメージ内)
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderflow
username: ${DB_USERNAME:orderflow}
password: ${DB_PASSWORD:defaultpass}
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
| 設定方法 | 優先度 | 適用シナリオ |
|---|---|---|
Docker Compose environment |
高 | 簡易デプロイ |
.env ファイル |
中 | ローカル開発 |
| K8s ConfigMap / Secret | 高 | 本番環境 |
# .env ファイル
DB_PASSWORD=secure_password_here
DB_ROOT_PASSWORD=root_secure_here
REDIS_PASSWORD=redis_secure_here
7. 総合サンプル:OrderFlowの完全Docker化
# Dockerfile
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080 8081
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-XX:+UseG1GC", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
# docker-compose.yml
version: "3.9"
services:
app:
build: .
ports:
- "8080:8080"
- "8081:8081"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
MANAGEMENT_SERVER_PORT: "8081"
depends_on:
mysql: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes: [mysql-data:/var/lib/mysql]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s; timeout: 5s; retries: 10
redis:
image: redis:7-alpine
volumes: [redis-data:/data]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s; timeout: 3s; retries: 5
volumes:
mysql-data:
redis-data:
❓ よくある質問
docker compose logs -f app でログを表示; 2) docker compose exec app sh でコンテナに入る (Alpineイメージ); 3) リモートデバッグ:ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]。depends_onとhealthcheckの関係は?depends_onはコンテナの起動順序のみを保証し, サービスの準備完了は保証しません。condition: service_healthyと併用すると, Docker Composeはヘルスチェックが通るまで待機してから依存サービスを起動します。mvn dependency:go-offlineを実行し, Dockerレイヤーキャッシュを活用; 2) .dockerignoreで無関係ファイルを除外; 3) BuildKitのキャッシュマウントを使用。ndots問題)。AlpineはJREのみのシナリオで推奨されます。📖 まとめ
- マルチステージDockerfile:Builderステージでコンパイル + ランタイムステージにはJARとJREのみ
.dockerignoreで無関係ファイルを除外; 先にpom.xmlをCOPYしてキャッシュレイヤーを活用- Docker Composeでマルチサービス編成:app + MySQL + Redis, ヘルスチェックで起動順序を制御
- 環境変数注入を設定; 機密情報は.envファイルまたはK8s Secretに保存
- イメージビルドにはEclipse Temurin JRE Alpine (~170MB)またはDistroless (~100MB)を推奨
- 非rootユーザーで実行; HEALTHCHECKは本番で必須
📝 練習問題
-
基本問題 (難易度:⭐): OrderFlowのDockerfileを書き, イメージをビルドしてローカルで実行し,
/actuator/healthが"UP"を返すことを確認してください。 -
応用問題 (難易度:⭐⭐):
docker-compose.ymlを書いてOrderFlow, MySQL, Redisの3サービスを編成し,healthcheckとdepends_onを設定して正しい起動順序を確保してください。 -
チャレンジ (難易度:⭐⭐⭐): イメージを150MB以下に最適化し, Distrolessベースイメージを使用し, JVMメモリパラメータ (
MaxRAMPercentage)を設定し, 非rootユーザーとセキュリティ関連設定を追加してください。



