Firebase Local Emulator Suite — Running a Firebase Bundle on a Laptop
Firebase Local Emulator Suite — Running a Firebase Bundle on a Laptop
Firebase started as a small startup in 2011 and was acquired by Google in 2014. Being a managed-first service, "no way to run it locally" was the right answer for a long time, but in March 2020 the picture changed when Firebase Local Emulator Suite reached GA.
1. About the Suite
The Firebase Local Emulator Suite ships inside the Firebase CLI (firebase-tools) as a bundle of local emulators. Nine emulators running on a Java VM boot in a single process, all visible via a single Web UI (default port 4000).
| Emulator | Default port | Emulates |
|---|---|---|
| Authentication | 9099 | Firebase Auth (email · OAuth · OTP · MFA). |
| Cloud Firestore | 8080 | NoSQL document DB. |
| Realtime Database | 9000 | JSON tree DB. |
| Cloud Storage | 9199 | Object storage (not S3 compatible). |
| Cloud Functions | 5001 | Serverless functions. |
| Cloud Pub/Sub | 8085 | Message queue. |
| Eventarc | 9299 | CloudEvents routing. |
| Hosting | 5000 | Static sites. |
| Firebase Extensions | (per ext) | Extension packages. |
The most important fact — FCM (Cloud Messaging) is not on this list. Firebase officially does not provide an emulator. The reason is simple — FCM has to traverse real device tokens and Google's push infrastructure, so simulation has no meaning. The "FCM emulators" floating around the internet are all unofficial wrappers or HTTP mocks.
2. One process, many ports
firebase emulators:start boots them all. Each emulator listens on its own port. SDKs auto-route to the emulator when env vars (FIRESTORE_EMULATOR_HOST · FIREBASE_AUTH_EMULATOR_HOST) are set, so no code changes are needed.
3. Persistence · Web UI
By default everything is in memory and disappears at exit. With --export-on-exit ./data + --import ./data, data exports on exit and imports on start. Alternatively, mount the emulator cache directory as a volume.
The Web UI (default port 4000) — adding a user directly in the Authentication tab issues an ID token immediately. The Firestore tab lets you add, edit, and delete documents directly. The fastest path for debugging the "API call → DB change" flow.
Firestore Security Rules — Mounting firestore.rules makes the emulator apply those rules. Denied SDK calls log the precise reason. The standard way to do regression tests for rules before production deploys.
4. Directly on the host
npm i -g firebase-tools
firebase init emulators
firebase emulators:start
firebase init emulators creates the emulator section of firebase.json, with an interactive selection of which emulators to enable.
5. Docker container
There is no official Docker image; the community image is the de facto standard.
services:
firebase:
image: andreysenov/firebase-tools:13.x
user: node
working_dir: /home/node
command:
- firebase
- emulators:start
- --project=demo
- --only=auth,firestore,storage,functions
ports:
- "4400:4400" # UI (default 4000)
- "9099:9099" # Auth
- "8085:8085" # Firestore
volumes:
- ./firebase.json:/home/node/firebase.json:ro
Image around 500 MB. First boot takes 30~60 seconds for emulator JAR download + Java startup.
6. SDK calls
Node.js (firebase-admin) — Auto-routes when env vars are set:
export FIREBASE_AUTH_EMULATOR_HOST=localhost:9099
export FIRESTORE_EMULATOR_HOST=localhost:8085
export FIREBASE_STORAGE_EMULATOR_HOST=localhost:9199
export GOOGLE_CLOUD_PROJECT=demo
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
initializeApp({ projectId: "demo" });
await getFirestore().collection("users").add({ name: "test-user" });
Web client (firebase) — Env vars are not auto-detected; call explicitly:
import { initializeApp } from "firebase/app";
import { connectAuthEmulator, getAuth } from "firebase/auth";
import { connectFirestoreEmulator, getFirestore } from "firebase/firestore";
const app = initializeApp({ projectId: "demo", apiKey: "fake-api-key" });
connectAuthEmulator(getAuth(app), "http://localhost:9099");
connectFirestoreEmulator(getFirestore(app), "localhost", 8085);
7. Limitations
No FCM — Verify server-side send calls separately with an HTTP mock like WireMock.
Firebase Hosting SSR — Partially supported. Some constraints when integrating with Functions.
Cloud Storage download URL — The URL the emulator issues differs in form from production. Patterns that store URLs in the DB need environment branching.
Performance Monitoring · Crashlytics · Remote Config — No emulator at all. Verify only in production.
App Check · App Hosting — Partial or no support. Check the official matrix.
Java dependency — JDK inside the container makes the image heavy (~500 MB+). Memory is also around 500 MB.
Single-instance assumption — One PC, one run. Sharing across teams requires a separate host.
8. Using in CI
- run: docker compose up -d firebase
- run: |
until curl -sf http://localhost:4400 >/dev/null; do sleep 2; done
- run: pnpm test
Polling for emulator readiness before tests is the key. Otherwise the first call hits ECONNREFUSED.
Closing thoughts
The most-searched gotcha for the Firebase Emulator Suite is that 9 of 9 minus FCM is the actual coverage. Setting env vars makes SDKs auto-route, so code changes are zero. Handling readiness polling well in CI makes it a standard channel for integration tests.
Next
- api-mocking-wiremock
Firebase Emulator Suite · Connect & Prototype · andreysenov/firebase-tools · FCM official (no emulator) for reference.