Docker: Phase 1 ミニプロジェクト: LEMP スタックのデプロイ

最終更新:2026-08-26

最初の 5 つのレッスンで学んだことを統合しましょう — LEMP スタック(Linux + Nginx + MySQL + PHP)をゼロから完全にデプロイします。これは Phase 1 の総合的な最終プロジェクトです。

1. 学べること



2. バックエンド開発者の実話

(1) 問題点: 開発環境の手動構築に半日かかる

Alice は社内管理システムの開発環境構築を任されました。Nginx が静的コンテンツを提供し、PHP-FPM が動的リクエストを処理し、MySQL がデータを保存します。手動インストールでは、Nginx の仮想ホスト設定、PHP-FPM と拡張モジュールのインストール、MySQL のインストールと初期化、そして 3 つのコンポーネント間の通信パスの設定が必要です。各ステップに落とし穴があり、同僚は丸 1 日かけてすべてを構築しました。

(2) Docker でのマルチコンテナデプロイソリューション

Alice は Docker を使って 10 分で完全な LEMP スタックを構築しました — 3 つのコンテナ、1 つのネットワーク、2 つのデータボリュームです。

BASH
# アーキテクチャ概要: Nginx → PHP-FPM → MySQL
docker network create lemp-net
docker volume create mysql-data

(3) メリット: 10 分 vs 半日

手動デプロイは半日かかり(再利用もできません)、Docker デプロイは 10 分で完了します(スクリプトは再利用可能でチーム全体で共有できます)。新メンバーのオンボーディングは、環境構築の丸 1 日から 15 分に短縮されました。



3. LEMP アーキテクチャ設計

(1) アーキテクチャ概要

100%
graph TB
    USER["ユーザーブラウザ<br/>:8080"] --> NGX["Nginx コンテナ<br/>:80<br/>静的ファイル + PHP プロキシ"]
    NGX -->|"FastCGI :9000"| PHP["PHP-FPM コンテナ<br/>:9000<br/>PHP 処理"]
    PHP -->|"MySQL :3306"| DB["MySQL コンテナ<br/>:3306<br/>データベース"]
    DB --> VOL["名前付きボリューム<br/>mysql-data<br/>永続ストレージ"]
    PHP --> PHPV["バインドマウント<br/>./src → /var/www/html<br/>PHP ソースコード"]
    NGX --> NGXV["バインドマウント<br/>./nginx.conf<br/>Nginx 設定"]

(2) 手動デプロイ vs Docker デプロイの比較

観点 手動デプロイ Docker デプロイ
所要時間 4〜8 時間 10 分
再現性 低(オペレーターの経験に依存) 高(スクリプト/コマンドを再利用)
可搬性 低(OS 依存) 高(プラットフォーム間で一貫)
環境の隔離 なし(システム環境を共有) 完全隔離
ロールバック 困難(アンインストールして再インストール) 容易(コンテナ削除して再ビルド)
チーム共有 ドキュメント作成 + 手順に従う コマンド/スクリプトを共有


4. 準備: ネットワークとボリューム

(1) カスタムネットワークの作成

コンテナ間の通信にはカスタムブリッジネットワークが必要です — デフォルトのブリッジネットワークはコンテナ名の DNS 解決をサポートしません。

BASH
# LEMP スタック用のカスタムブリッジネットワークを作成
docker network create lemp-net

# 確認
docker network ls | grep lemp-net

(2) データボリュームの作成

MySQL のデータは永続化する必要があります — コンテナが削除されてもデータが失われてはいけません。

BASH
# MySQL データ用の名前付きボリュームを作成
docker volume create mysql-data

# 確認
docker volume ls | grep mysql-data

(3) ボリュームマウント戦略の比較

戦略 構文 ユースケース 永続性
名前付きボリューム -v mysql-data:/var/lib/mysql データベース、永続ストレージ ✅ コンテナ削除後も保持
バインドマウント -v ./src:/var/www/html 開発環境のコード同期 ✅ ホストディレクトリにマッピング
tmpfs --tmpfs /tmp 一時データ、機密情報 ❌ コンテナ停止時に消失

5. MySQL コンテナをデプロイする

▶ サンプル: MySQL コンテナの起動 (難易度: ⭐⭐)

BASH
# 永続ストレージ付きで MySQL 8.0 を起動
docker run -d \
  --name lemp-mysql \
  --network lemp-net \
  -e MYSQL_ROOT_PASSWORD=rootsecret \
  -e MYSQL_DATABASE=appdb \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -v mysql-data:/var/lib/mysql \
  --restart=unless-stopped \
  mysql:8.0
💡 ヒント: まず MySQL を起動し、初期化が完了するまで(約 30 秒)待ってから他のコンテナを起動してください。docker logs -f lemp-mysql で初期化の進捗を監視できます。「ready for connections」が表示されたら初期化完了です。

(1) MySQL が動作していることを確認

BASH
# MySQL が動作中か確認
docker ps --filter name=lemp-mysql

# MySQL に接続して確認
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e "SELECT 1;"


6. Nginx コンテナをデプロイする

(1) Nginx 設定の準備

Nginx を FastCGI プロキシとして設定し、PHP リクエストを PHP-FPM コンテナに転送する必要があります。

BASH
# Nginx 設定を作成
cat > lemp-project/nginx.conf << 'EOF'
server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass lemp-php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}
EOF
📌 重要: fastcgi_pass lemp-php:9000lemp-php は PHP-FPM コンテナ名です。同じカスタムネットワーク lemp-net 内にあるため、Docker DNS が自動的にコンテナ名を IP アドレスに解決します。

▶ サンプル: Nginx コンテナの起動 (難易度: ⭐⭐)

BASH
# Nginx コンテナを起動
docker run -d \
  --name lemp-nginx \
  --network lemp-net \
  -p 8080:80 \
  -v $(pwd)/lemp-project/src:/var/www/html \
  -v $(pwd)/lemp-project/nginx.conf:/etc/nginx/conf.d/default.conf \
  --restart=unless-stopped \
  nginx:1.25-alpine


7. 検証とテスト

▶ サンプル: PHP ページにアクセスして検証 (難易度: ⭐)

BASH
# PHP 情報ページをテスト
curl http://localhost:8080/info.php | head -5

# データベース接続をテスト
curl http://localhost:8080/db-test.php
💻 出力:

TEXT 📖 参照専用
# curl http://localhost:8080/db-test.php
Connected to MySQL successfully!
MySQL version: 8.0.35
✅ 確認ポイント: 「Connected to MySQL successfully!」が表示されていれば、3 つのコンテナ Nginx → PHP-FPM → MySQL が正常に連携しています。

(1) 完全な検証チェックリスト

テスト項目 コマンド 期待結果
3 つのコンテナすべてが動作中 docker ps 3 つのコンテナが「Up」状態
PHP ページが正常に表示 curl localhost:8080/info.php PHP 設定情報が返る
データベース接続が正常 curl localhost:8080/db-test.php 「Connected successfully」
コンテナ間の DNS 解決 docker exec lemp-nginx ping -c 2 lemp-php ping 成功
データボリュームが存在 docker volume ls mysql-data がリストに含まれる


8. データ永続化の検証

▶ サンプル: MySQL コンテナクラッシュのシミュレーション (難易度: ⭐⭐⭐)

BASH
# 1. テストテーブルとデータを作成
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e \
  "CREATE TABLE test_items (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100)); INSERT INTO test_items (name) VALUES ('Alice'), ('Bob');"

# 2. データが存在することを確認
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e \
  "SELECT * FROM test_items;"

# 3. MySQL コンテナを停止して削除
docker stop lemp-mysql && docker rm lemp-mysql

# 4. 同じボリュームで MySQL コンテナを再作成
docker run -d \
  --name lemp-mysql \
  --network lemp-net \
  -e MYSQL_ROOT_PASSWORD=rootsecret \
  -e MYSQL_DATABASE=appdb \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -v mysql-data:/var/lib/mysql \
  --restart=unless-stopped \
  mysql:8.0

# 5. MySQL の起動を待ち、データがまだ存在することを確認
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e \
  "SELECT * FROM test_items;"
💻 出力:

TEXT 📖 参照専用
+----+-------+
| id | name  |
+----+-------+
|  1 | Alice |
|  2 | Bob   |
+----+-------+
✅ 確認ポイント: コンテナを削除して再作成してもデータが残っている — 名前付きボリュームによる永続化が機能しています。



9. コンテナの起動順序と依存関係ポリシー

戦略 方法 信頼性
手動順次起動 MySQL → PHP → Nginx 中(経験に依存)
ヘルスチェック待機 MySQL の準備ができるまで PHP の再起動を待つ 高(自動化)
再起動戦略 --restart=on-failure 失敗したサービスを自動再試行 中(順序は保証されない)
Docker Compose depends_on + ヘルスチェック(Phase 3 で学習) 高(宣言的)
💡 ヒント: このレッスンでは手動の起動順序を使用しています。Phase 3 では、Docker Compose の depends_on を使って依存関係管理を自動化する方法を学びます。



10. 完全なサンプル: LEMP スタックのワンクリックデプロイスクリプト

BASH
# ============================================
# 完全なウォークスルー: LEMP スタックをゼロからデプロイ
# カバー: ネットワーク、ボリューム、MySQL、PHP、Nginx
# ============================================

# --- セットアップ ---
# ネットワークとボリュームを作成
docker network create lemp-net
docker volume create mysql-data

# プロジェクトディレクトリとファイルを作成
mkdir -p lemp-project/src

cat > lemp-project/src/index.php << 'PHPEOF'
<?php
$host = 'lemp-mysql';
$db   = 'appdb';
$user = 'appuser';
$pass = 'apppass';
try {
    $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
    echo "<h1>LEMP Stack is working!</h1>";
    echo "<p>MySQL version: " . $pdo->query("SELECT VERSION()")->fetchColumn() . "</p>";
} catch (PDOException $e) {
    echo "<h1>Database connection failed</h1><p>" . $e->getMessage() . "</p>";
}
?>
PHPEOF

cat > lemp-project/nginx.conf << 'NGINXEOF'
server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ {
        fastcgi_pass lemp-php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
NGINXEOF

# --- デプロイ ---
# 1. MySQL を起動(最初に起動する必要がある)
docker run -d \
  --name lemp-mysql \
  --network lemp-net \
  -e MYSQL_ROOT_PASSWORD=rootsecret \
  -e MYSQL_DATABASE=appdb \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -v mysql-data:/var/lib/mysql \
  --restart=unless-stopped \
  mysql:8.0

# 2. MySQL の初期化を待つ(約 30 秒)
echo "Waiting for MySQL to initialize..."
sleep 30

# 3. PHP-FPM を起動
docker run -d \
  --name lemp-php \
  --network lemp-net \
  -v $(pwd)/lemp-project/src:/var/www/html \
  --restart=unless-stopped \
  php:8.2-fpm

# 4. Nginx を起動
docker run -d \
  --name lemp-nginx \
  --network lemp-net \
  -p 8080:80 \
  -v $(pwd)/lemp-project/src:/var/www/html \
  -v $(pwd)/lemp-project/nginx.conf:/etc/nginx/conf.d/default.conf \
  --restart=unless-stopped \
  nginx:1.25-alpine

# --- 検証 ---
echo "Verifying deployment..."
docker ps
curl -s http://localhost:8080/index.php
💻 出力(抜粋):

TEXT 📖 参照専用
# docker ps
CONTAINER ID   IMAGE            STATUS         PORTS                  NAMES
c1d2e3f4       nginx:1.25-alpine  Up 5 min      0.0.0.0:8080->80/tcp   lemp-nginx
b2c3d4e5       php:8.2-fpm      Up 5 min       9000/tcp               lemp-php
a1b2c3d4       mysql:8.0        Up 5 min       3306/tcp, 33060/tcp    lemp-mysql

# curl http://localhost:8080/index.php
<h1>LEMP Stack is working!</h1><p>MySQL version: 8.0.35</p>

❓ よくある質問

Q なぜ個別に起動するのではなく Compose を使わないのですか?
A これは Phase 1 の総合演習であり、各コンテナが独立して作成・接続される方法を理解することを目的としています。Phase 3 では、これらの docker run コマンドを 1 つの YAML ファイルで置き換える Docker Compose について学びます。自動化に移行する前に手動で起動することで、より深い理解が得られます。
Q Nginx は PHP-FPM にどのように接続しますか?
A FastCGI プロトコル経由です。Nginx 設定の fastcgi_pass lemp-php:9000 が PHP-FPM コンテナを指します。lemp-php はコンテナ名で、同じカスタムネットワーク内では Docker DNS が自動的に IP アドレスに解決するため、IP アドレスをハードコードする必要はありません。
Q コンテナの再起動後もデータは残りますか?
A 名前付きボリューム(-v mysql-data:/var/lib/mysql)を使ってマウントされたデータは、コンテナが削除された後も残ります。ただし、バインドマウント(-v ./src:/var/www/html)のソースコードはホストディレクトリにマッピングされているため、ホストディレクトリが削除されなければコードは失われません。
Q エラーの原因となっているコンテナをどうやって特定しますか?
A 3 ステップのトラブルシューティング方法: ① docker ps -a でステータスが「Up」でないコンテナを確認; ② docker logs <コンテナ> でエラーログを確認; ③ docker inspect <コンテナ> --format='{{.State.ExitCode}}' で終了コードを確認。よくある問題: MySQL の準備ができていないときに PHP 接続エラーが発生します。MySQL の初期化が完了するまで待つだけで解決します。
Q 複数のコンテナからのログを同時に表示するにはどうすればよいですか?
A docker compose logs(Phase 3 で学習)で集約表示できます。今は次の方法で対応可能: ① 各コンテナを個別に docker logs; ② docker logs -f で複数のターミナルウィンドウを開いて同時に監視; ③ ログをファイルにリダイレクトしてマージして表示。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度: ⭐): LEMP スタックをゼロからデプロイし、http://localhost:8080/index.php にアクセスして 3 つのコンテナが正常に連携していることを確認してください。
  2. 応用問題 (難易度: ⭐⭐): MySQL コンテナのクラッシュをシミュレート(docker stop lemp-mysql && docker rm lemp-mysql)し、再ビルド後にデータベースのデータが永続化されているか確認してください。
  3. 挑戦問題 (難易度: ⭐⭐⭐): Adminer(データベース管理ツール)コンテナを追加して MySQL に接続し、Web インターフェースでデータベースを管理してください。ヒント: docker run -d --name adminer --network lemp-net -p 8081:8080 adminer; http://localhost:8081 にアクセスし、appuser/apppass でログインしてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%