Local, reproducible emulator stack for app development and testing.
Includes Firebase (Auth / Firestore / Storage / Pub/Sub / Eventarc / Tasks), Spanner (+ pgAdapter), Neo4j, Elasticsearch, Qdrant, and A2A Inspector. Handy Go-based CLIs and just tasks make day‑to‑day work fast and predictable.
- Quick Start
- Services Overview
- Developer Commands
- Testing
- Configuration
- Access
- Data & Persistence
- CLI Tools (details)
- Troubleshooting
- CI
Pre-flight (recommended)
# Validate gcloud user login + ADC and common misconfigurations
just gcloud-auth-check
# or: bash scripts/check-gcloud-auth.sh --details --strict --verbose
Note: The Firebase emulator does not need production credentials, but Google SDKs and tools may still read ADC. Running the pre‑flight helps catch expired tokens, missing files, or account mismatches before you start.
Start emulators (recommended)
just start
Stop emulators (recommended)
just stop
Or use Docker Compose directly
docker compose up -d
# ... later
docker compose down
All services run on the emulator-network
. Defaults are shown; you can change
ports via .env.local
.
Service | Container | Ports (host → container) | Health | CLI |
---|---|---|---|---|
Bigtable | bigtable-emulator |
8086 → 8086 (gRPC) | TCP | ✓ |
Firebase UI | firebase-emulator |
4000 → 4000 | HTTP | – |
Firestore | firebase-emulator |
8080 → 8080 | TCP | – |
Auth | firebase-emulator |
9099 → 9099 | TCP | – |
Pub/Sub | firebase-emulator |
9399 → 9399 | HTTP | – |
Storage | firebase-emulator |
9199 → 9199 | HTTP | – |
Eventarc | firebase-emulator |
9299 → 9299 | HTTP | – |
Tasks | firebase-emulator |
9499 → 9499 | HTTP | – |
Spanner gRPC | spanner-emulator |
9010 → 9010 | TCP | – |
Spanner REST | spanner-emulator |
9020 → 9020 | TCP | – |
pgAdapter | pgadapter-emulator |
55432 → 5432 (PostgreSQL) | TCP | ✓ |
Neo4j | neo4j-emulator |
7474 → 7474 (HTTP), 7687 → 7687 (Bolt) | HTTP | ✓ |
Elasticsearch | elasticsearch-emulator |
9200 → 9200 (REST), 9300 → 9300 | HTTP | ✓ |
Qdrant | qdrant-emulator |
6333 → 6333 (REST), 6334 → 6334 (gRPC) | HTTP | ✓ |
A2A Inspector | a2a-inspector |
8081 → 8080 | HTTP | – |
MLflow | mlflow-server |
5252 → 5000 | HTTP | – |
PostgreSQL | postgres-18 |
5433 → 5432 (PostgreSQL) | TCP | ✓ |
Note: Set
A2A_INSPECTOR_REPO=<git-url>
and/orA2A_INSPECTOR_REF=<git-ref>
beforejust start
to pin the upstream inspector checkout. The image builds via the locala2a-inspector/Dockerfile
, which fetches the repository and runs on Python 3.12 to satisfy its runtime requirement.
CLI availability (✓) means a matching Go-based REPL is included and runnable via Docker Compose profiles.
Convenience tasks (install just from https://just.systems):
just test
— Run pytest throughuv run
just gcloud-auth-check
— Pre-flight gcloud auth/ADC check (strict + details)just gh-validate
— Validate GitHub workflow files usingwrkflw
just gh-run
— Execute a workflow locally (Docker / Podman / Emulation)just gh-tui
— Openwrkflw
TUI and manage workflows interactively
Run all tests
just test
Coverage highlights
- Firestore: Create/Get via REST (runs with emulator-specific permissive rules)
- Auth: SignUp / SignInWithPassword
- Storage: Upload/Download (continues even when the bucket-creation API is unimplemented)
- Pub/Sub: Topic / Subscription / Publish / Pull / Ack via REST
- Eventarc: Port connectivity smoke test
- Spanner / pgAdapter: Container liveness and TCP port checks
- Neo4j / Elasticsearch / Qdrant: Health verification
Test groups
- Smoke: CLI help/info/exit sanity checks
- CRUD: End‑to‑end create/read flows per CLI (with cleanup)
- Features: Aggregations, relationships, filtered vector search
- Negative: pgAdapter/Spanner incompat tests (e.g., missing PK, sequence)
About skipped tests (expected)
- Pub/Sub REST: Some emulator builds or environments may return 500s because of HTTP/2 requirements or unstable REST responses.
- Tests use aiohttp and skip instead of fail when responses are flaky.
- On environments where the emulator mandates HTTP/2, the test skips automatically.
- Cloud Tasks REST: Frequently unimplemented; we detect that ahead of time and skip.
Note: Firestore security rules are switched to fully permissive mode for the local emulator to simplify tests. Never use this configuration in production.
All emulators use the same project ID: test-project
.
Copy example envs and/or export in your shell.
# Core project configuration
export CLOUDSDK_CORE_PROJECT=test-project
export GOOGLE_CLOUD_PROJECT=test-project
export FIREBASE_PROJECT_ID=test-project
# Firebase Emulator hosts
export FIREBASE_AUTH_EMULATOR_HOST=localhost:9099
export FIRESTORE_EMULATOR_HOST=localhost:8080
export FIREBASE_STORAGE_EMULATOR_HOST=localhost:9199
export PUBSUB_EMULATOR_HOST=localhost:9399
# Spanner Emulator host
export SPANNER_EMULATOR_HOST=localhost:9010
export BIGTABLE_EMULATOR_HOST=localhost:8086
export POSTGRES_PORT=5433
export PGADAPTER_PORT=55432
# Authentication (empty for emulators)
export GOOGLE_APPLICATION_CREDENTIALS=""
# Optional
export CLOUD_TASKS_EMULATOR_HOST=localhost:9090
export EVENTARC_EMULATOR=localhost:9299
export JAVA_TOOL_OPTIONS="-Xmx4g"
export CLOUDSDK_SURVEY_DISABLE_PROMPTS=1
Docker-to-Docker networking
export FIREBASE_AUTH_EMULATOR_HOST=firebase:9099
export FIRESTORE_EMULATOR_HOST=firebase:8080
export FIREBASE_STORAGE_EMULATOR_HOST=firebase:9199
export PUBSUB_EMULATOR_HOST=firebase:9399
export SPANNER_EMULATOR_HOST=spanner:9010
export BIGTABLE_EMULATOR_HOST=bigtable-emulator:8086
# MLflow client (inside another container)
export MLFLOW_TRACKING_URI=http://mlflow:5000
Local host client (MLflow)
```bash
export MLFLOW_TRACKING_URI=http://localhost:5252
macOS Port Conflicts
- On macOS, Control Center (AirPlay) commonly binds ports
5000
and7000
. - Avoid exposing emulator services on these host ports. This repo uses
5252
for MLflow by default.
- pgAdapter (PostgreSQL): host
localhost
, port55432
, useruser
, dbtest-instance
- Bigtable: gRPC
localhost:8086
- Neo4j: Bolt
localhost:7687
, HTTPlocalhost:7474
(neo4j / password) - Elasticsearch: REST
localhost:9200
- Qdrant: REST
localhost:6333
- A2A Inspector:
http://localhost:8081
- MLflow UI:
http://localhost:5252
- PostgreSQL: host
localhost
, port5433
, userpostgres
, dbpostgres
Note (A2A Inspector): Entering localhost
in the web UI resolves inside the container; use host.docker.internal
to reach the host.
- Firebase data persists under
firebase/data/
. - Firebase emulator exports on exit automatically (export-on-exit).
just stop
simply stops containers. - MLflow experiment data (backend + artifacts) persists under
mlflow-data/
.
Docs
PostgreSQL CLI
docker compose --profile cli run --rm postgres-cli
PostgreSQL 18 verification
just pg-verify
Run with Docker Compose profiles. Examples:
pgAdapter CLI
docker compose --profile cli run --rm pgadapter-cli
Neo4j CLI
docker compose --profile cli run --rm neo4j-cli
Bigtable CLI
docker compose --profile cli run --rm bigtable-cli
Elasticsearch CLI
docker compose --profile cli run --rm elasticsearch-cli
Qdrant CLI
docker compose --profile cli run --rm qdrant-cli
All CLIs support multi‑line input and print tabular results for readability.
Apple Silicon (arm64)
- Bigtable emulator image is amd64-only; the compose service sets
platform: linux/amd64
to run via emulation. - Performance impact is usually small for local testing. If you prefer native arm64, consider running the emulator directly on host via
gcloud
.
Health checks
just check
Tips
- Firebase UI may take 30–60s to fully start.
- Firestore root endpoint does not return 200; use port listen checks and UI.
- Inspect logs:
docker compose logs -f <service>
- Test from inside container:
docker compose exec <service> curl ...
- Verify messages like "ready", "started", or "listening" in logs.
GitHub Actions workflow .github/workflows/test-emulators.yaml
:
- Installs dependencies via
uv sync --all-extras --frozen
(lockfile に準拠) - Runs tests via
uv run pytest
Use just gh-validate
locally to sanity‑check workflow files with wrkflw
.
When using pgAdapter (PostgreSQL protocol) on top of Spanner, prefer PostgreSQL type names and be aware of some limitations. Quick mapping:
Concept | Spanner (GoogleSQL) | PostgreSQL (via pgAdapter) |
---|---|---|
Integer | INT64 |
BIGINT |
String (fixed length) | STRING(n) |
VARCHAR(n) |
String (unbounded) | STRING |
TEXT |
Floating point | FLOAT64 |
DOUBLE PRECISION |
Exact decimal | NUMERIC |
NUMERIC |
Timestamp | TIMESTAMP |
TIMESTAMPTZ |
Primary key | PRIMARY KEY (id) |
id BIGINT PRIMARY KEY or table-level PK |
Notes
- Every table must have a PRIMARY KEY (Spanner requirement).
SERIAL
/SEQUENCE
are generally unsupported.- Some PostgreSQL features may be limited compared to stock PostgreSQL.
- Keep DDL simple (explicit PK, basic types) for best compatibility.
Highlights (stock PostgreSQL, not pgAdapter):
- Asynchronous I/O for reads improves performance in sequential scan, bitmap heap scan, and VACUUM read paths (benchmarks show up to ~3x on targeted operations)
uuidv7()
function to generate time-ordered UUIDs (millisecond timestamp prefix)- Generated columns: support for virtual generated columns in addition to stored
- OAuth 2.0 authentication support and upgrade-safe statistics retention
How to use
- Start with the rest of the suite:
just start
- CLI (Docker profile):
docker compose --profile cli run --rm postgres-cli
- Host access:
psql -h localhost -p ${POSTGRES_PORT:-5433} -U postgres -d postgres
- Quick verification of version,
uuidv7()
, and generated columns:just pg-verify
Notes
- This PostgreSQL 18 service is independent from Spanner/pgAdapter. Use it when you need exact PostgreSQL behavior or to try 18-specific features.
- Inside Docker, connect to
postgres:5432
. From the host, the default mapped port is5433
(change viaPOSTGRES_PORT
).
Extensions
- Preinstalled:
pgvector
,postgis
- Auto-enabled on first initialization via
postgres/docker-entrypoint-initdb.d/01-extensions.sql
- If you created the data volume before this change, either run
CREATE EXTENSION
manually inside the DB or remove thepostgres_data
volume to trigger the init script