GCP on localhost:4588

A free, MIT-licensed Google Cloud emulator. Give your coding agents a real GCP to build and test against: no project, no billing account, no auth token.

# Install floci-cli, then start floci-gcp
curl -fsSL https://floci.io/install.sh | sh
floci gcp start
eval $(floci gcp env)
27 GCP services ready on :4588. GCS · Pub/Sub · Firestore · Datastore · IAM · +22 more

24 ms startup · ~13 MiB idle · MIT licensed · no GCP project

Homebrew, Windows and Docker options

Every way to run it.

One CLI installs and runs all four emulators: use Homebrew, the install script or PowerShell. Or skip the CLI and run the Docker image.

1 · Install floci CLI

brew install floci-io/floci/floci

2 · Start the emulator

floci gcp start

3 · Export env vars

eval $(floci gcp env)
# exports STORAGE_EMULATOR_HOST,
# PUBSUB_EMULATOR_HOST, FIRESTORE_EMULATOR_HOST,
# DATASTORE_EMULATOR_HOST, SECRET_MANAGER_EMULATOR_HOST

# gcloud ignores those; point it at floci-gcp too
# (any token works locally)
export CLOUDSDK_API_ENDPOINT_OVERRIDES_STORAGE=http://localhost:4588/
export CLOUDSDK_CORE_PROJECT=floci-local
export CLOUDSDK_AUTH_ACCESS_TOKEN=floci

4 · Upload a file

# Create a bucket
gcloud storage buckets create gs://my-bucket

# Write a file and upload it
echo "No billing alert? Must be floci." \
  > hello-floci.txt
gcloud storage cp hello-floci.txt \
  gs://my-bucket/hello-floci.txt

# Download it back and read it
gcloud storage cp \
  gs://my-bucket/hello-floci.txt \
  hello-back.txt
cat hello-back.txt

Teach your coding agent

# Asks which agents to install it for
npx skills add floci-io/floci-cli

The Floci skill teaches Claude Code, Codex, Cursor, Kiro and other coding agents to run the emulators with floci-cli.

Other useful commands

# Check status and server health
floci gcp status

# List enabled services
floci gcp services

# View logs
floci gcp logs

# Stop the emulator
floci gcp stop

# Export env for selected services only
eval $(floci gcp env --service gcs,pubsub)

Start with persistence

floci gcp start --persist ./data
# state survives container restarts

Diagnose issues

floci gcp doctor
# checks Docker, gcloud, connectivity

Client libraries and gcloud, side by side.

Point your existing GCP client libraries at localhost:4588. No code changes beyond the endpoint.

Python · google-cloud-storage
# pip install google-cloud-storage
import os
from google.cloud import storage

os.environ["STORAGE_EMULATOR_HOST"] = "http://localhost:4588"
client = storage.Client(project="floci-local")

# create a bucket and upload a file
bucket = client.lookup_bucket("demo-bucket") or client.create_bucket("demo-bucket")
blob = bucket.blob("data/hello.txt")
blob.upload_from_string("hello from floci-gcp!")

# list objects in the bucket
for b in client.list_blobs("demo-bucket"):
    print(b.name, b.size)

# download and read back
print(blob.download_as_text())
gcloud CLI
# point gcloud at floci-gcp (any token works locally)
export CLOUDSDK_API_ENDPOINT_OVERRIDES_STORAGE=http://localhost:4588/
export CLOUDSDK_CORE_PROJECT=floci-local
export CLOUDSDK_AUTH_ACCESS_TOKEN=floci

# create a bucket
gcloud storage buckets describe gs://demo-bucket >/dev/null 2>&1 \
  || gcloud storage buckets create gs://demo-bucket

# upload a file
echo "hello from floci-gcp!" > hello.txt
gcloud storage cp hello.txt gs://demo-bucket/hello.txt

# list objects
gcloud storage ls gs://demo-bucket

# download it back
gcloud storage cp gs://demo-bucket/hello.txt -
Python · google-cloud-pubsub
# pip install google-cloud-pubsub
import os
from google.api_core.exceptions import AlreadyExists
from google.cloud import pubsub_v1

os.environ["PUBSUB_EMULATOR_HOST"] = "localhost:4588"
project = "floci-local"

publisher = pubsub_v1.PublisherClient()
subscriber = pubsub_v1.SubscriberClient()

# create topic and subscription
topic = publisher.topic_path(project, "my-topic")
sub = subscriber.subscription_path(project, "my-sub")
try:
    publisher.create_topic(request={"name": topic})
    subscriber.create_subscription(
        request={"name": sub, "topic": topic}
    )
except AlreadyExists:
    pass  # already created on an earlier run

# publish a message
future = publisher.publish(topic, b"hello from floci-gcp!")
print("Published: " + str(future.result()))

# pull and acknowledge
resp = subscriber.pull(
    request={"subscription": sub, "max_messages": 1}
)
for msg in resp.received_messages:
    print(msg.message.data.decode())
    subscriber.acknowledge(
        request={"subscription": sub,
                 "ack_ids": [msg.ack_id]}
    )
Node.js · @google-cloud/pubsub
// npm install @google-cloud/pubsub
process.env.PUBSUB_EMULATOR_HOST = 'localhost:4588';
const { PubSub } = require('@google-cloud/pubsub');
const pubsub = new PubSub({ projectId: 'floci-local' });

async function run() {
  const [topic] = await pubsub
    .topic('my-topic').get({ autoCreate: true });
  const [sub] = await topic
    .subscription('my-sub').get({ autoCreate: true });

  // publish a message
  const msgId = await topic.publishMessage({
    data: Buffer.from('hello from floci-gcp!'),
  });
  console.log('Published: ' + msgId);

  // receive one message
  sub.on('message', msg => {
    console.log(msg.data.toString());
    msg.ack();
    sub.removeAllListeners();
  });
}
run();
Python · google-cloud-firestore
# pip install google-cloud-firestore
import os
from google.cloud import firestore

os.environ["FIRESTORE_EMULATOR_HOST"] = "localhost:4588"
db = firestore.Client(project="floci-local")

# write a document
ref = db.collection("users").document("alice")
ref.set({"name": "Alice", "score": 42})

# read it back
doc = ref.get()
print(doc.to_dict())

# query with a filter
for doc in (
    db.collection("users")
      .where("score", ">=", 40)
      .stream()
):
    print(doc.id, doc.to_dict())
Node.js · @google-cloud/firestore
// npm install @google-cloud/firestore
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:4588';
const { Firestore } = require('@google-cloud/firestore');
const db = new Firestore({ projectId: 'floci-local' });

async function run() {
  // write a document
  const ref = db.collection('users').doc('alice');
  await ref.set({
    name: 'Alice', score: 42, active: true
  });

  // read it back
  const doc = await ref.get();
  console.log(doc.data());

  // query with a filter
  const snap = await db.collection('users')
    .where('score', '>=', 40).get();
  snap.forEach(d => console.log(d.id, d.data()));
}
run();
Python · google-cloud-secret-manager
# pip install google-cloud-secret-manager
import grpc
from google.api_core.exceptions import AlreadyExists
from google.cloud import secretmanager
from google.cloud.secretmanager_v1.services.secret_manager_service.transports.grpc import (
    SecretManagerServiceGrpcTransport,
)

# the Python client ignores *_EMULATOR_HOST, so point it at floci-gcp over plain gRPC
transport = SecretManagerServiceGrpcTransport(
    channel=grpc.insecure_channel("localhost:4588")
)
client = secretmanager.SecretManagerServiceClient(transport=transport)
parent = "projects/floci-local"

# create the secret (skipped if it exists)
name = parent + "/secrets/db-password"
try:
    client.create_secret(request={
        "parent": parent,
        "secret_id": "db-password",
        "secret": {"replication": {"automatic": {}}},
    })
except AlreadyExists:
    pass

# store the value
client.add_secret_version(request={
    "parent": name,
    "payload": {"data": b"super-secret-value"},
})

# read the latest version
version = name + "/versions/latest"
resp = client.access_secret_version(request={"name": version})
print(resp.payload.data.decode())
gcloud CLI
# point gcloud at floci-gcp (any token works locally)
export CLOUDSDK_API_ENDPOINT_OVERRIDES_SECRETMANAGER=http://localhost:4588/
export CLOUDSDK_CORE_PROJECT=floci-local
export CLOUDSDK_AUTH_ACCESS_TOKEN=floci

# gcloud can't add secret versions on floci-gcp yet: store them from a
# client library (left), then read and list them here
gcloud secrets list

# read the latest version
gcloud secrets versions access latest \
  --secret db-password

The client libraries you already use.

Point the official Google Cloud client libraries and gcloud at localhost:4588. No project, no service account key.

Emulator variables, set for you

floci gcp env exports STORAGE, PUBSUB, FIRESTORE, DATASTORE and SECRET_MANAGER *_EMULATOR_HOST variables for the libraries that read them.

gcloud included

gcloud ignores those variables, so one endpoint override and any access token point it at floci-gcp instead.

Real containers where it matters

Cloud Run, Cloud SQL, GKE (on k3s) and Managed Kafka (on Redpanda) run real containers through Docker.

Native-image speed

Starts in 24 ms, idles at ~13 MiB and works offline, so CI never waits on the network.

Google Cloud, without the project.

MIT licensed and built for developer workflows. No GCP project, no billing account, no auth token. Just floci-cli and localhost.

Other emulators: AWSAzureOCI

license
MIT
real credentials
none
cloud account
none
cost
$0