[お試し] LAMP+MongoDB+Firebase環境をDockerで構築
Docker
Dockerって「チームで使うもの」と思われがちですが、実は一人でも“未来の自分への配布ツール”として大きな意味があります。
LAMP+MongoDB+Firebase という複雑な構成を、手元で壊して試し、もう一度起動し直せる——その安心感は圧倒的です。
2つの Docker コンテナで実現
[LAMP + MongoDB] + [Firebase Emulator] を Docker で構築することで、まるで「クラウド環境を手元に再現」したような開発環境が整います。学習・テスト・動作確認に最適で、トラブル時の切り分けも容易です。
構成
「lamp-mongo」がLAMP+MongoDB環境で、「firebase-emulator」がFIREBASE環境です。examplesでは、それら環境でテストできるスクリプトを格納します。
docker-multi-dev-env/
├── lamp-mongo/(LAMP+Mコンテナ)
│ ├── Dockerfile
│ └── docker-compose.yml
├── firebase-emulator/(仮想Firebaseコンテナ)
│ ├── docker-compose.yml
│ └── firebase.json
├── examples/(PHPサンプル集)
│ └── lamp-mongo-php/
│ ├── composer.json
│ ├── index.php
│ ├── mongo-*.php
│ └── etc...
Dockerをいれる
dnf -y update <- 時間かかる
dnf -y install dnf-plugins-core curl
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker
docker -v
Docker version 28.0.4, build b8034c0
docker compose version
Docker Compose version v2.34.0
今回の Dockerコンテナ環境は、Githubから落としてね
では、ビルドします。
cd /..DOKOKANO PATH../docker-cent9-lamp-mongo-firebase/
docker compose -f lamp-mongo/docker-compose.yml build
...
...
[+] Building 1/1
? lamp Built
と、でれば、ビルド完了です。そのコンテナを立ち上げます。
LAMP+Mongo コンテナ立ち上げます。
バックグラウンド
docker compose -f lamp-mongo/docker-compose.yml up -d
docker ps <- これで確認できます。
ログを見ながら起動
docker compose -f lamp-mongo/docker-compose.yml up
その場のサーバから確認(curl)phpinfoが確認できます。
curl http://localhost:8080
ブラウザで確認
http://[VPSのIPアドレス]:8080
で、 設定を変えて 立ち上げるときは、キャッシュに注意
docker compose -f lamp-mongo/docker-compose.yml build --no-cache
docker compose -f lamp-mongo/docker-compose.yml up -d --force-recreate
MongoDB の接続を確認
curl http://localhost:8080/00.mongo-connect.php
<h1>MongoDB 接続成功!</h1>
次がDB確認。まだ作ってないのでゼロです。
curl http://localhost:8080/01.mongo-list-dbs.php
<h1>MongoDB データベース一覧</h1><p>admin</p><p>config</p><p>local</p>
MongoDB はこんな感じで構築できるので、
MongoDB(インスタンス)
└── データベース(Database)
└── コレクション(Collection)
└── ドキュメント(Document)
<?php
require 'vendor/autoload.php';
try {
$client = new MongoDB\Client("mongodb://mongo:27017");
$collection = $client->sampledb001->users;
// データ挿入
$insertResult = $collection->insertOne(['name' => 'Taro', 'email' => 'taro@example.com']);
echo "<p>挿入: " . $insertResult->getInsertedId() . "</p>";
// データ取得
$user = $collection->findOne(['name' => 'Taro']);
echo "<p>取得: " . $user['name'] . " (" . $user['email'] . ")</p>";
// データ更新
$collection->updateOne(['name' => 'Taro'], ['$set' => ['email' => 'newtaro@example.com']]);
echo "<p>更新完了</p>";
// データ削除
$collection->deleteOne(['name' => 'Taro']);
echo "<p>削除完了</p>";
} catch (Exception $e) {
echo "<h1>エラー: " . $e->getMessage() . "</h1>";
}
?>
ってなPHPを叩くと
curl http://localhost:8080/02.mongo-crud.php
<p>挿入: 67ef50edec478068170a5b22</p><p>取得: Taro (taro@example.com)</p><p>更新完了</p><p>削除完了</p>
作って、取得して、削除しています。が、データベースである「sampledb001」は残っているので、
curl http://localhost:8080/01.mongo-list-dbs.php
<h1>MongoDB データベース一覧</h1><p>admin</p><p>config</p><p>local</p><p>sampledb001</p>
「sampledb001」データベースが確認できます。
つづきやして「sampledb002」で、Usersテーブルの型を決めます。これは上記だとただ好き勝手なjsonをいれて、検索して~更新/削除みたいになっていて、運用ベースだと⚠危険なので、テーブル設計します。それをスキーマいいます。
<?php
require 'vendor/autoload.php';
use MongoDB\Client;
use MongoDB\Exception\Exception;
try {
$client = new Client("mongodb://mongo:27017");
$db = $client->sampledb002;
$db->dropCollection('validated_users'); // 先に削除
$db->createCollection('validated_users', [
'validator' => [
'$jsonSchema' => [
'bsonType' => 'object',
'required' => ['name', 'email'],
'properties' => [
'name' => ['bsonType' => 'string'],
'email' => ['bsonType' => 'string'],
'age' => ['bsonType' => 'int']
]
]
]
]);
echo "<h1>コレクション作成成功(バリデーション付き)</h1>";
} catch (Exception $e) {
echo "<h1>作成失敗: " . $e->getMessage() . "</h1>";
}
?>
curl http://localhost:8080/10.mongo-create-collection.php
<h1>コレクション作成成功(バリデーション付き)</h1>
さっきと同様に、Usersにはnameとemail(ついでにint型も体験するため年齢を追加)です。
この設定したスキーマを確認するんですが、listCollections() では validator が出なかったため、$db->commandで確認する11.mongo-get-schema.phpで叩くと...
curl http://localhost:8080/11.mongo-get-schema.php
<h1>validated_users コレクションのスキーマ:</h1><pre>MongoDB\Model\BSONDocument Object
(
[storage:ArrayObject:private] => Array
(
[bsonType] => object
[required] => MongoDB\Model\BSONArray Object
(
[storage:ArrayObject:private] => Array
(
[0] => name
[1] => email
)
)
[properties] => MongoDB\Model\BSONDocument Object
(
[storage:ArrayObject:private] => Array
(
[name] => MongoDB\Model\BSONDocument Object
(
[storage:ArrayObject:private] => Array
(
[bsonType] => string
)
)
[email] => MongoDB\Model\BSONDocument Object
(
[storage:ArrayObject:private] => Array
(
[bsonType] => string
)
)
[age] => MongoDB\Model\BSONDocument Object
(
[storage:ArrayObject:private] => Array
(
[bsonType] => int
)
)
)
)
)
)
</pre>
今回はそういう型しか受け付けません。なので、12.mongo-insert-invalid.phpでは、わざと失敗してみます。
<?php
require 'vendor/autoload.php';
use MongoDB\Client;
use MongoDB\Exception\Exception;
use MongoDB\Driver\Exception\BulkWriteException;
try {
$client = new Client("mongodb://mongo:27017");
$collection = $client->sampledb002->validated_users;
$collection->insertOne([
'name' => '田中一郎',
// emailフィールドがない → バリデーション違反
'age' => 20
]);
echo "<h1>挿入成功(バリデーションをすり抜け?)</h1>";
} catch (BulkWriteException $e) {
echo "<h1>MongoDB バリデーションエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
} catch (Exception $e) {
echo "<h1>その他のエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
}
?>
スキーマのバリエーションエラーの場合は、catch/Exceptionより深い部分で、エラーになるためBulkWriteExceptionで拾わないといけないみたいです。そうしないと無言スルーされます。
curl http://localhost:8080/12.mongo-insert-invalid.php
<h1>MongoDB バリデーションエラー:</h1><pre>Document failed validation</pre>
13.mongo-insert-valid.phpでは、普通にinsertします。
curl http://localhost:8080/13.mongo-insert-valid.php
<h1>バリデーションOK:挿入成功</h1>
<?php
require 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://mongo:27017");
$collection = $client->sampledb002->validated_users;
use MongoDB\Driver\Exception\BulkWriteException;
try {
$result = $collection->insertOne([
'name' => '佐藤花子',
'email' => 'hanako@example.com',
'age' => 25
]);
echo "<h1>バリデーションOK:挿入成功</h1>";
} catch (BulkWriteException $e) {
echo "<h1>MongoDB バリデーションエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
} catch (Exception $e) {
echo "<h1>その他のエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
}
?>
14.mongo-find-all.phpで、ドキュメントを確認します。
<?php
require 'vendor/autoload.php';
use MongoDB\Client;
try {
$client = new Client("mongodb://mongo:27017");
$collection = $client->sampledb002->validated_users;
$cursor = $collection->find();
echo "<h1>validated_users コレクションの内容:</h1><pre>";
foreach ($cursor as $doc) {
print_r($doc);
}
echo "</pre>";
} catch (BulkWriteException $e) {
echo "<h1>MongoDB バリデーションエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
} catch (Exception $e) {
echo "<h1>その他のエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
}
?>
curl http://localhost:8080/14.mongo-find-all.php
<h1>validated_users コレクションの内容:</h1><pre>MongoDB\Model\BSONDocument Object
(
[storage:ArrayObject:private] => Array
(
[_id] => MongoDB\BSON\ObjectId Object
(
[oid] => 67ef6284593e2251260f2b32
)
[name] => 佐藤花子
[email] => hanako@example.com
[age] => 25
)
)
ほれみたことか!
...つづいで、15.mongo-update-invalid.phpで更新もあえて転んでみます。
<?php
require 'vendor/autoload.php';
use MongoDB\Client;
use MongoDB\Exception\Exception;
use MongoDB\Driver\Exception\BulkWriteException;
try {
$client = new Client("mongodb://mongo:27017");
$collection = $client->sampledb002->validated_users;
$collection->updateOne(
['name' => '佐藤花子'],
['$set' => ['age' => '若い']] // age は int が必要 → バリデーション違反
);
echo "<h1>更新成功(バリデーションをすり抜け?)</h1>";
} catch (BulkWriteException $e) {
echo "<h1>MongoDB バリデーションエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
} catch (Exception $e) {
echo "<h1>その他のエラー:</h1>";
echo "<pre>" . $e->getMessage() . "</pre>";
}
?>
curl http://localhost:8080/15.mongo-update-invalid.php
<h1>MongoDB バリデーションエラー:</h1><pre>Document failed validation</pre>
はい。では最後に統計をチェックしましょう。
curl http://localhost:8080/16.mongo-dbstats.php
<h1>sampledb002 の統計情報</h1>
<pre>MongoDB\Model\BSONDocument Object
(
[storage:ArrayObject:private] => Array
(
[db] => sampledb002
[collections] => 1
[views] => 0
[objects] => 1
[avgObjSize] => 84
[dataSize] => 84
[storageSize] => 20480
[indexes] => 1
[indexSize] => 20480
[totalSize] => 40960
[scaleFactor] => 1
[fsUsedSize] => 32177713152
[fsTotalSize] => 426708545536
[ok] => 1
)
)
</pre>
<h1>sampledb002->validated_users の統計情報</h1>
ドキュメント数: 1
データサイズ: 84 bytes
平均ドキュメントサイズ: 84 bytes
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://mongo:27017");
$db = $client->sampledb002;
$stats = $db->command(['dbStats' => 1])->toArray()[0];
echo "<h1>sampledb002 の統計情報</h1>\n<pre>";
print_r($stats);
echo "</pre>\n";
$collStats = $db->command(['collStats' => 'validated_users'])->toArray()[0];
echo "<h1>sampledb002->validated_users の統計情報</h1>\n";
echo "ドキュメント数: " . $collStats['count'] . "\n";
echo "データサイズ: " . $collStats['size'] . " bytes\n";
echo "平均ドキュメントサイズ: " . $collStats['avgObjSize'] . " bytes\n";
?>
こんな感じでしたー。では続いて、仮想FIREBASEを体験しましょう。
ツリーはこうなっています。
firebase-emulator/
├── Dockerfile
├── docker-compose.yml
├── firebase.json
└── functions/
├── package.json
├── tsconfig.json
└── src/
└── index.ts
docker compose -f firebase-emulator/docker-compose.yml down #起動をやり直すとき一回落とす
docker compose -f firebase-emulator/docker-compose.yml build --no-cache
docker compose -f firebase-emulator/docker-compose.yml up -d #dオプでバックグラウンド
docker compose -f firebase-emulator/docker-compose.yml ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
firebase-emulator-firebase-1 firebase-emulator-firebase "docker-entrypoint.s…" firebase 3 minutes ago Up 8 seconds 0.0.0.0:4000->4000/tcp, [::]:4000->4000/tcp, 0.0.0.0:5001->5001/tcp, [::]:5001->5001/tcp, 0.0.0.0:8088->8088/tcp, [::]:8088->8088/tcp, 0.0.0.0:9099->9099/tcp, [::]:9099->9099/tcp
起動出来たら、Cloud functionをTypecsrictにしているので、トランスパイルします。
docker compose -f firebase-emulator/docker-compose.yml exec firebase npx tsc
これがエラーの時は以下で
docker compose -f firebase-emulator/docker-compose.yml exec firebase ./node_modules/.bin/tsc
typescriptからjsに変換するコマンドなんですが、外から実行するとうまく動かないケースはあるそうです。コマンドの内容を変えたり、Docker内に入って、実行するとトランスパイルできます。
それでは、LAMPコンテナからFirebaseを操作してみます。
これで仮想cloud functionをテストできます。
curl http://localhost:5001/demo-project/us-central1/helloWorld
Hello from Firebase Emulator!
demo-projectというのは、Dockerfileで、そういうデモとわかりやすいプロジェクト名にしています。変えることも可能です。us-central1というリージョンはシミュレーターの仕様で特にこだわりがなければ勝手にus-central1ということでシミュレートされます。
Firestoreも御覧の通り
curl http://localhost:5001/demo-project/us-central1/helloFirestore
Document written!
でローカルでFirebaseが起動しており、Firestoreにデータが入っていることを確認できます。
Discussion