Zenn
🐳

[お試し] 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から落としてね

https://github.com/meta-taro/docker-cent9-lamp-mongo-firebase

では、ビルドします。

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!

http://localhost:4000/
でローカルでFirebaseが起動しており、Firestoreにデータが入っていることを確認できます。

Discussion

ログインするとコメントできます