〜Firebase初学者の素朴な疑問から理解する、Firebaseの本当の仕組み〜
はじめに
先日、Claude Desktop・Antigravity・Geminiという複数のAIツールを組み合わせて、画像を20秒ごとに自動切替表示するデジタルサイネージWebアプリを開発しました。Firebase(Firestore / Storage / Authentication / Hosting)を使った、いわゆる「バイブコーディング」での開発です。
https://digital-ffc47.web.app/signage
アプリは無事に完成して公開できたのですが、開発が終わったあと、ふとものすごく素朴な疑問が頭に浮かびました。
「テストモードで作ったものが、なんでそのまま本番アプリとして完成するの?」
だって「テスト」モードですよ。テスト用に作ったものなら、本番用に作り直すのが普通では……? 模試の答案がそのまま本試験の合格通知になるみたいで、なんだか狐につままれた気分でした。
AIに質問するのも一瞬ためらうような初歩的な疑問でしたが、思い切って聞いてみたところ、Firebaseの設計思想の核心に触れる答えが返ってきました。この記事では、その疑問と答えを通して「Firebaseのテストモードとは何なのか」「なぜテストモードのまま本番に化けるのか」を、初学者目線で整理します。
同じモヤモヤを抱えたまま if true; を貼り付けている方の参考になれば嬉しいです。
疑問が生まれるまで:開発の流れをざっくり振り返る
まず前提として、サイネージアプリ開発でFirebaseをどう使ったかを簡単に。
- Firestore Database を「テストモードで開始」で作成(画像のメタデータ保存用)
- Storage も「テストモード」で作成(画像ファイル本体の保存用)
- アプリを動かして、アップロード・表示の動作確認
- 完成間際に Authentication(Googleログイン) を追加
- セキュリティルールを「ログイン必須」の厳しい内容に書き換え
- Firebase Hostingでデプロイして公開
この流れ、チュートリアルや解説記事でもよく見る王道パターンだと思います。でも手順をなぞっているだけだと、ステップ1〜2で選んだ「テストモード」がいつ「本番モード」になったのか、切り替えボタンを押した記憶がないんですよね。
「本番環境に移行する」という操作をした覚えがないのに、気づいたら本番アプリとして動いている。ここが疑問の出発点でした。
Q1. そもそも「テストモード」とは何なのか?
一言でいうと、「誰でも出入り自由な、鍵の開いた家」の状態のことです。
FirestoreやStorageを作成するとき「テストモードで開始」を選ぶと、セキュリティルールが次のような状態になります。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// 誰でも読み書きOK(期限付き)
allow read, write: if request.time < timestamp.date(2026, 7, 12);
}
}
}
ポイントは allow read, write の条件が実質的に無条件で許可になっていること。つまりテストモードの正体は、特別な環境でもモードでもなく、ゆるゆるのセキュリティルールが最初から書かれた状態にすぎません。
なぜ最初はゆるゆるにするのか
開発初期にいきなり「ログインしていないとダメ」「画像ファイル以外はダメ」というガチガチのルールがかかっていると、エラーが出たときに
- プログラム(コード)が間違っているのか
- セキュリティルールに弾かれているのか
の切り分けができず、初学者は確実に迷子になります。実際、私もFirestoreのルール貼り付けで「Parse error」が出たとき(※開いていた画面がRealtime Databaseだったという別の罠でしたが)、原因の切り分けにかなり時間を食いました。
そこで、まずはルールを全開放しておいて、「画像をアップロードする」「データを表示する」というアプリの基本機能だけに集中して作る(=テストする)。これがテストモードの目的です。「アプリのロジックのテスト」に専念するための、一時的なセキュリティ設定なんですね。
Q2. テストモードで作ったものが、なぜそのまま本番になるのか?
ここが今回いちばん「なるほど!」となったところです。
結論:Firebaseには「テスト用データベース」と「本番用データベース」という区別が、そもそも存在しないからです。
開発の最初から最後まで、触っているのはずっと同じ1つの本物のデータベース(Storage)。「テストモード」と「本番モード」の違いは、データベース本体の違いではなく、入り口に立っている「門番(セキュリティルール)」が緩いか厳しいかの違いだけなんです。
図にするとこうなります。
【テストモード】
ユーザー ──→ 門番「どうぞどうぞ(if true)」──→ データベース本体
【本番モード】
ユーザー ──→ 門番「ログインは? ファイルサイズは?」──→ データベース本体
↑
中身は同じ箱!
サイネージアプリで実際に起きていたこと
この仕組みを、自分の開発に当てはめて振り返ってみます。
フェーズ1:テストモード(開発初期)
match /signage_images/{doc} {
allow read, write: if true; // 誰でも読み書きOK
}
鍵が開けっぱなしの家。この状態で、画像のアップロードと20秒切替表示の動作を確認しました。
フェーズ2:本番モードへの移行(完成時)
Googleログイン機能を追加したあと、ルールをこう書き換えました。
match /signage_images/{doc} {
allow read: if true; // 表示は誰でもOK
allow write: if request.auth != null; // 書き込みはログイン必須
}
Storage側も同様に、「読み取り自由・書き込みはログイン必須+5MB以下+画像形式のみ」へ変更。
実は、このルールを書き換えて「公開」ボタンを押した瞬間こそが、「テストモード」から「本番モード」への切り替えだったのです。切り替えボタンを押した記憶がないのは当然で、ルールの書き換えそのものが切り替え操作だったんですね。
データベースの中身も、保存された画像も、URLも何ひとつ変わっていない。変わったのは門番の指示書だけ。それなのに、システムとしては「誰でも書き込める危険な状態」から「認証済みユーザーしか操作できない安全な本番アプリ」に生まれ変わっている——これがFirebaseの設計の賢いところです。
この仕組みを知って腑に落ちたこと
「テストモード」という名前のミスリード
初学者が混乱する最大の原因は、おそらくモードという言葉です。「モード」と聞くと、ゲームのイージーモード/ハードモードのように「別の環境に切り替わる」イメージを持ってしまいますが、実態は単なるルールのプリセット(初期テンプレート)。
個人的には「テストモードで開始」よりも「門番なしで開始」とでも呼んでくれたほうが、よほど実態に即している気がします。
「二段階の進め方」が合理的な理由
まずテストモードで動かす → 動いたら認証を入れてルールを締める
この進め方は単なる慣習ではなく、Firebaseの「データベースは1つ、ルールで守る」という設計を前提にした、最初から本番を見据えた合理的な開発フローだったわけです。テスト用に作ったものを捨てて作り直すのではなく、同じものを育てて仕上げる。だから「テストモードで作ったものがそのまま本番で完成する」のです。
期限付きルールの意味も分かる
テストモードのデフォルトルールには request.time < timestamp.date(...) のような約30日の有効期限が付いています。これも仕組みを知ると意味が分かります。「同じ本物のデータベースを全開放している」状態は本来とても危険なので、「締め忘れたら強制的に閉まる」安全装置が入っているんですね。期限が来るとFirebaseから警告メールが届くのも同じ理由です。
まとめ
- テストモードは特別な環境ではなく、「全開放のセキュリティルール」が書かれた初期状態のこと
- Firebaseにテスト用/本番用のデータベースの区別はなく、最初から最後まで同じ本物を使っている
- テストモード⇔本番モードの違いは「門番(ルール)」の緩さ・厳しさだけ
- ルールを厳格化して公開した瞬間が、本番移行の瞬間
「テストモードで作ったものが、なぜ本番で完成するのか」——この素朴な疑問の答えは、Firebaseの「インフラは共通、安全性はルールで制御する」という設計思想そのものでした。
初歩的すぎるかなと思う質問ほど、聞いてみると仕組みの核心につながっている。AIに気軽に「これってアホな質問かもしれないんですが……」と聞ける時代だからこそ、素朴な疑問を放置せずに言語化していくのが、初学者の最強の学習法なのかもしれません。
参考:今回の開発で使ったルール(最終形)
Firestore
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /signage_images/{doc} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
Storage
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /signage/{file} {
allow read: if true;
allow write: if request.auth != null
&& request.resource.size < 5 * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
}
}
同じ疑問を持った方の「なるほど!」につながれば嬉しいです。