{"id":50389036,"url":"https://github.com/floci-io/floci-gcp","last_synced_at":"2026-05-30T17:01:49.666Z","repository":{"id":359834169,"uuid":"1241679700","full_name":"floci-io/floci-gcp","owner":"floci-io","description":"Light, fluffy, and always free - GCP Local Emulator","archived":false,"fork":false,"pushed_at":"2026-05-23T17:04:55.000Z","size":1501,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T18:22:28.337Z","etag":null,"topics":["docker","floci","floci-gcp","gcp","google"],"latest_commit_sha":null,"homepage":"https://floci.io","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/floci-io.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["hectorvent"]}},"created_at":"2026-05-17T17:26:18.000Z","updated_at":"2026-05-23T17:04:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/floci-io/floci-gcp","commit_stats":null,"previous_names":["floci-io/floci-gcp"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/floci-io/floci-gcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floci-io%2Ffloci-gcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floci-io%2Ffloci-gcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floci-io%2Ffloci-gcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floci-io%2Ffloci-gcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/floci-io","download_url":"https://codeload.github.com/floci-io/floci-gcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floci-io%2Ffloci-gcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33700863,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","floci","floci-gcp","gcp","google"],"created_at":"2026-05-30T17:01:47.301Z","updated_at":"2026-05-30T17:01:49.653Z","avatar_url":"https://github.com/floci-io.png","language":"Java","funding_links":["https://github.com/sponsors/hectorvent"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"floci_banner.svg\" alt=\"floci-gcp\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eLight, fluffy, and always free — GCP Local Emulator\u003c/strong\u003e\u003cbr /\u003e\n  No account. No auth token. No feature gates. Just \u003ccode\u003edocker compose up\u003c/code\u003e.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/floci-io/floci-gcp/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/hectorvent/floci-gcp?label=latest%20release\u0026color=blue\" alt=\"Latest Release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/floci-io/floci-gcp/actions/workflows/release.yml\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/hectorvent/floci-gcp/release.yml?label=build\" alt=\"Build Status\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://hub.docker.com/r/floci/floci-gcp\"\u003e\u003cimg src=\"https://img.shields.io/docker/pulls/floci/floci-gcp?label=docker%20pulls\" alt=\"Docker Pulls\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://hub.docker.com/r/floci/floci-gcp\"\u003e\u003cimg src=\"https://img.shields.io/docker/image-size/floci/floci-gcp/latest?label=image%20size\" alt=\"Docker Image Size\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-green\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/floci-io/floci-gcp/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/hectorvent/floci-gcp?style=flat\" alt=\"GitHub Stars\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e ·\n  \u003ca href=\"#features\"\u003eFeatures\u003c/a\u003e ·\n  \u003ca href=\"#supported-services\"\u003eServices\u003c/a\u003e ·\n  \u003ca href=\"#sdk-integration\"\u003eSDKs\u003c/a\u003e ·\n  \u003ca href=\"#testcontainers\"\u003eTestcontainers\u003c/a\u003e ·\n  \u003ca href=\"#compatibility-testing\"\u003eCompatibility\u003c/a\u003e ·\n  \u003ca href=\"https://floci.io/floci-gcp/\"\u003eDocs\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## What is floci-gcp?\n\nfloci-gcp is a free, open-source local GCP emulator for development, testing, and CI.\n\nIt gives you GCP-shaped services on your machine without requiring a cloud account, auth token, or paid feature gates. Point your GCP SDK, gcloud CLI, Terraform, or test suite at `http://localhost:4588` and keep your existing workflows.\n\nfloci-gcp is named after [floccus](https://en.wikipedia.org/wiki/Cirrocumulus_floccus), the cloud formation that looks like popcorn.\n\n## Quick Start\n\n```yaml\n# docker-compose.yml\nservices:\n  floci-gcp:\n    image: floci/floci-gcp:latest\n    ports:\n      - \"4588:4588\"\n    volumes:\n      - ./data:/app/data\n    environment:\n      FLOCI_GCP_HOSTNAME: floci-gcp\n      FLOCI_GCP_BASE_URL: http://floci-gcp:4588\n```\n\n```bash\ndocker compose up -d\n```\n\nExport the GCP emulator environment variables:\n\n```bash\nexport PUBSUB_EMULATOR_HOST=localhost:4588\nexport FIRESTORE_EMULATOR_HOST=localhost:4588\nexport DATASTORE_EMULATOR_HOST=localhost:4588\nexport STORAGE_EMULATOR_HOST=http://localhost:4588\nexport SECRET_MANAGER_EMULATOR_HOST=localhost:4588\nexport GOOGLE_CLOUD_PROJECT=floci-local\n```\n\nAll GCP services are available at `http://localhost:4588`. Credentials are not validated.\n\n\u003cdetails\u003e\n\u003csummary\u003eUsing Docker directly?\u003c/summary\u003e\n\n```bash\ndocker run -d --name floci-gcp \\\n  -p 4588:4588 \\\n  floci/floci-gcp:latest\n```\n\n\u003c/details\u003e\n\n## Features\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eLocal GCP without the cloud account\u003c/strong\u003e\u003c/summary\u003e\n\nRun GCP-compatible services locally without a GCP account, service account key, or paid feature gates.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eSingle port for everything\u003c/strong\u003e\u003c/summary\u003e\n\nAll GCP services — gRPC and REST — share a single port (`4588`) via HTTP/2 ALPN negotiation. No per-service daemon setup, no port management.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eReal GCP wire protocols\u003c/strong\u003e\u003c/summary\u003e\n\nfloci-gcp speaks the same protocols as real GCP: protobuf-over-gRPC for Pub/Sub, Firestore, and Secret Manager; binary HTTP/protobuf for Datastore; REST XML and JSON for Cloud Storage. Existing SDK calls work without modification.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eFast enough for CI\u003c/strong\u003e\u003c/summary\u003e\n\nThe native image starts in milliseconds and keeps idle memory low, making it practical for local development and test pipelines.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eConfigurable persistence\u003c/strong\u003e\u003c/summary\u003e\n\nChoose from in-memory, persistent, hybrid, and write-ahead log storage depending on the durability profile you need.\n\n\u003c/details\u003e\n\n## Why floci-gcp?\n\nGCP's official emulators are fragmented — each service ships its own binary, runs on a different port, and requires separate setup. floci-gcp unifies them under a single port.\n\n| Capability | floci-gcp | GCP official emulators |\n|---|:---:|:---:|\n| Single port for all services | ✅ | ❌ |\n| gRPC + REST on the same port | ✅ | ❌ |\n| No GCP account required | ✅ | ✅ |\n| Pub/Sub | ✅ | ✅ |\n| Firestore | ✅ | ✅ |\n| Datastore | ✅ | ✅ |\n| Cloud Storage (GCS) | ✅ | ⚠️ Limited |\n| Secret Manager | ✅ | ❌ |\n| IAM | ✅ | ❌ |\n| Managed Kafka | ✅ | ❌ |\n| Native binary | ✅ | ❌ |\n\n## Architecture Overview\n\n```mermaid\nflowchart LR\n    Client[\"GCP SDK / gcloud CLI\"]\n\n    subgraph FlociGCP [\"floci-gcp, port 4588\"]\n        Router[\"HTTP/2 Router\\nALPN negotiation\"]\n\n        subgraph GRPC [\"gRPC services\"]\n            A[\"Pub/Sub\\nFirestore\\nSecret Manager\"]\n        end\n\n        subgraph REST [\"REST services\"]\n            B[\"Cloud Storage\\nIAM\\nDatastore\"]\n        end\n\n        subgraph Docker [\"Docker-backed\"]\n            C[\"Managed Kafka\\n(Redpanda)\"]\n        end\n\n        Router --\u003e GRPC\n        Router --\u003e REST\n        Router --\u003e Docker\n        GRPC \u0026 REST --\u003e Store[(\"StorageBackend\\nmemory · hybrid · persistent · wal\")]\n    end\n\n    DockerEngine[\"Docker Engine\"]\n    Client --\u003e|\"HTTP/2 :4588\\nGCP wire protocols\"| Router\n    Docker --\u003e|\"Docker API\"| DockerEngine\n```\n\n## Supported Services\n\nfloci-gcp emulates GCP services across storage, messaging, identity, and managed infrastructure.\n\n| Category | Services |\n|---|---|\n| Object and document storage | Cloud Storage (GCS), Firestore, Datastore |\n| Messaging | Pub/Sub, Managed Kafka |\n| Security and identity | Secret Manager, IAM |\n\n\u003cdetails\u003e\n\u003csummary\u003eDetailed service notes\u003c/summary\u003e\n\n| Service | Protocol | Notable features |\n|---|---|---|\n| **Cloud Storage (GCS)** | REST XML + REST JSON | Buckets, objects, multipart upload, object compose, ACLs, bucket IAM, conditional requests (preconditions), versioning, lifecycle, CORS, pre-signed URLs (V4) |\n| **Pub/Sub** | gRPC | Topics, subscriptions, publish, pull, streaming pull, push delivery, snapshots, seek, field masks on update |\n| **Firestore** | gRPC | Documents, collections, queries (all operators), field transforms, aggregation (COUNT), transactions, batch writes, real-time listeners (`listen` stream) |\n| **Datastore** | HTTP/protobuf | Entities, structured queries, GQL queries, aggregation (COUNT), transactions, GQL named/positional bindings |\n| **Secret Manager** | gRPC | Secrets, versioning, access, `versions/latest` alias, disable/enable/destroy, IAM bindings |\n| **IAM** | REST JSON | Service accounts, RSA-2048 key pairs (JSON key file format), policy bindings, `SignBlob` (V4 signed URLs) |\n| **Managed Kafka** | REST JSON | Clusters, topics, consumer groups; Redpanda-backed or mock mode |\n\n\u003c/details\u003e\n\n## Persistence \u0026 Storage Modes\n\nfloci-gcp supports flexible storage modes. Configure globally via `FLOCI_GCP_STORAGE_MODE`.\n\n| Mode | Behavior | Best for | Durability |\n|:---:|---|---|:---:|\n| **`memory`** | **(Default)** Entirely in-RAM. Lost on container stop. | Speed, CI pipelines | ❌ None |\n| **`persistent`** | Every write goes directly to disk synchronously. | Durable local dev | ✅ Good |\n| **`hybrid`** | In-memory with async flush every 5 seconds. | Balance of speed and safety | ✅ Good |\n| **`wal`** | Write-Ahead Log. Every mutation written to disk immediately. | Maximum durability | 💎 Highest |\n\nUse `memory` for fast CI runs. Use `hybrid` when you want state preserved across container restarts.\n\n## Multi-Project Isolation\n\nGCP resource names follow `projects/{project}/...`. floci-gcp uses the project ID as the multi-tenancy boundary — resources in `project-a` are invisible to `project-b`.\n\nThe project ID is resolved in this order:\n1. URL path segment `projects/{project}/...`\n2. `x-goog-request-params` header (`project=...`)\n3. `FLOCI_GCP_DEFAULT_PROJECT_ID` fallback (default: `floci-local`)\n\n```bash\n# Two projects, full isolation\nexport PUBSUB_EMULATOR_HOST=localhost:4588\n\ngcloud pubsub topics create my-topic --project=project-a\ngcloud pubsub topics create my-topic --project=project-b\n\n# Each project has its own independent topic\n```\n\n## SDK Integration\n\nPoint your existing GCP SDK at `http://localhost:4588`.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eJava (GCP SDK)\u003c/strong\u003e\u003c/summary\u003e\n\n```java\n// Pub/Sub\nManagedChannel channel = ManagedChannelBuilder\n    .forTarget(\"localhost:4588\")\n    .usePlaintext()\n    .build();\n\nTransportChannelProvider channelProvider =\n    FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel));\nCredentialsProvider credentialsProvider = NoCredentialsProvider.create();\n\nTopicAdminClient topicClient = TopicAdminClient.create(\n    TopicAdminSettings.newBuilder()\n        .setTransportChannelProvider(channelProvider)\n        .setCredentialsProvider(credentialsProvider)\n        .build());\n\ntopicClient.createTopic(TopicName.of(\"floci-local\", \"my-topic\"));\n```\n\n```java\n// Cloud Storage\nStorage storage = StorageOptions.newBuilder()\n    .setHost(\"http://localhost:4588\")\n    .setProjectId(\"floci-local\")\n    .setCredentials(NoCredentials.getInstance())\n    .build()\n    .getService();\n\nstorage.create(BucketInfo.of(\"my-bucket\"));\nstorage.create(BlobInfo.newBuilder(\"my-bucket\", \"hello.txt\").build(),\n    \"hello from floci-gcp\".getBytes());\n```\n\n```java\n// Firestore\nFirestoreOptions options = FirestoreOptions.newBuilder()\n    .setHost(\"localhost:4588\")\n    .setProjectId(\"floci-local\")\n    .setCredentials(NoCredentials.getInstance())\n    .build();\n\nFirestore db = options.getService();\ndb.collection(\"users\").add(Map.of(\"name\", \"Alice\", \"age\", 30)).get();\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003ePython (google-cloud)\u003c/strong\u003e\u003c/summary\u003e\n\n```python\nimport os\nos.environ[\"PUBSUB_EMULATOR_HOST\"] = \"localhost:4588\"\n\nfrom google.cloud import pubsub_v1\n\npublisher = pubsub_v1.PublisherClient()\ntopic_path = publisher.topic_path(\"floci-local\", \"my-topic\")\npublisher.create_topic(request={\"name\": topic_path})\nfuture = publisher.publish(topic_path, b\"hello from floci-gcp\")\nfuture.result()\n```\n\n```python\nimport os\nos.environ[\"STORAGE_EMULATOR_HOST\"] = \"http://localhost:4588\"\n\nfrom google.cloud import storage\n\nclient = storage.Client(project=\"floci-local\")\nbucket = client.bucket(\"my-bucket\")\nclient.create_bucket(bucket)\n\nblob = bucket.blob(\"hello.txt\")\nblob.upload_from_string(\"hello from floci-gcp\")\nprint(blob.download_as_text())\n```\n\n```python\nimport os\nos.environ[\"FIRESTORE_EMULATOR_HOST\"] = \"localhost:4588\"\n\nfrom google.cloud import firestore\n\ndb = firestore.Client(project=\"floci-local\")\ndb.collection(\"users\").add({\"name\": \"Alice\", \"age\": 30})\ndocs = db.collection(\"users\").where(\"name\", \"==\", \"Alice\").stream()\nfor doc in docs:\n    print(doc.to_dict())\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eNode.js\u003c/strong\u003e\u003c/summary\u003e\n\n```javascript\nimport { PubSub } from \"@google-cloud/pubsub\";\n\nprocess.env.PUBSUB_EMULATOR_HOST = \"localhost:4588\";\n\nconst pubsub = new PubSub({ projectId: \"floci-local\" });\nawait pubsub.createTopic(\"my-topic\");\nconst [subscription] = await pubsub.topic(\"my-topic\").createSubscription(\"my-sub\");\n```\n\n```javascript\nimport { Storage } from \"@google-cloud/storage\";\n\nconst storage = new Storage({\n  apiEndpoint: \"http://localhost:4588\",\n  projectId: \"floci-local\",\n});\n\nawait storage.createBucket(\"my-bucket\");\nawait storage.bucket(\"my-bucket\").file(\"hello.txt\").save(\"hello from floci-gcp\");\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eGo\u003c/strong\u003e\u003c/summary\u003e\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n\n    \"cloud.google.com/go/pubsub\"\n    \"google.golang.org/api/option\"\n    \"google.golang.org/grpc\"\n    \"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    conn, err := grpc.Dial(\"localhost:4588\", grpc.WithTransportCredentials(insecure.NewCredentials()))\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    client, err := pubsub.NewClient(ctx, \"floci-local\",\n        option.WithGRPCConn(conn))\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer client.Close()\n\n    topic, err := client.CreateTopic(ctx, \"my-topic\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    fmt.Println(\"Created topic:\", topic.ID())\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eBash (gcloud CLI)\u003c/strong\u003e\u003c/summary\u003e\n\n```bash\nexport PUBSUB_EMULATOR_HOST=localhost:4588\ngcloud config set project floci-local\n\n# Pub/Sub\ngcloud pubsub topics create my-topic\ngcloud pubsub subscriptions create my-sub --topic=my-topic\ngcloud pubsub topics publish my-topic --message=\"hello from floci-gcp\"\ngcloud pubsub subscriptions pull my-sub --auto-ack\n\n# Cloud Storage\nexport STORAGE_EMULATOR_HOST=http://localhost:4588\ngcloud storage buckets create gs://my-bucket\necho \"hello\" | gcloud storage cp - gs://my-bucket/hello.txt\ngcloud storage ls gs://my-bucket\n\n# Secret Manager\nexport SECRET_MANAGER_EMULATOR_HOST=localhost:4588\ngcloud secrets create my-secret --replication-policy=automatic\necho -n \"my-value\" | gcloud secrets versions add my-secret --data-file=-\ngcloud secrets versions access latest --secret=my-secret\n```\n\n\u003c/details\u003e\n\n## Testcontainers\n\nUse `GenericContainer` to start an isolated floci-gcp instance directly from your tests. This avoids shared state, manual daemon setup, and port conflicts.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eJava\u003c/strong\u003e\u003c/summary\u003e\n\n```java\n@Testcontainers\nclass PubSubIntegrationTest {\n\n    @Container\n    static GenericContainer\u003c?\u003e flociGcp = new GenericContainer\u003c\u003e(\"floci/floci-gcp:latest\")\n        .withExposedPorts(4588)\n        .waitingFor(Wait.forHttp(\"/_floci/health\").forPort(4588));\n\n    static TopicAdminClient topicClient;\n\n    @BeforeAll\n    static void setup() throws Exception {\n        String host = flociGcp.getHost();\n        int port = flociGcp.getMappedPort(4588);\n\n        ManagedChannel channel = ManagedChannelBuilder\n            .forAddress(host, port)\n            .usePlaintext()\n            .build();\n\n        topicClient = TopicAdminClient.create(\n            TopicAdminSettings.newBuilder()\n                .setTransportChannelProvider(\n                    FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel)))\n                .setCredentialsProvider(NoCredentialsProvider.create())\n                .build());\n    }\n\n    @Test\n    void shouldCreateTopic() {\n        topicClient.createTopic(TopicName.of(\"floci-local\", \"test-topic\"));\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003ePython\u003c/strong\u003e\u003c/summary\u003e\n\n```python\nimport pytest\nfrom testcontainers.core.container import DockerContainer\nfrom google.cloud import pubsub_v1\n\n\n@pytest.fixture(scope=\"session\")\ndef floci_gcp():\n    with DockerContainer(\"floci/floci-gcp:latest\").with_exposed_ports(4588) as container:\n        container.get_exposed_port(4588)  # wait for startup\n        yield container\n\n\ndef test_pubsub(floci_gcp):\n    port = floci_gcp.get_exposed_port(4588)\n    host = floci_gcp.get_container_host_ip()\n\n    import os\n    os.environ[\"PUBSUB_EMULATOR_HOST\"] = f\"{host}:{port}\"\n\n    publisher = pubsub_v1.PublisherClient()\n    topic_path = publisher.topic_path(\"floci-local\", \"test-topic\")\n    publisher.create_topic(request={\"name\": topic_path})\n```\n\n\u003c/details\u003e\n\n## Compatibility Testing\n\nThe [`compatibility-tests`](./compatibility-tests/) directory validates floci-gcp across SDKs and IaC tools.\n\n| Module | Language / Tool | SDK / Client |\n|---|---|---|\n| `sdk-test-java` | Java | GCP SDK for Java |\n| `sdk-test-node` | Node.js | `@google-cloud/*` |\n| `sdk-test-python` | Python | `google-cloud-*` |\n| `sdk-test-go` | Go | `cloud.google.com/go/*` |\n| `compat-terraform` | Terraform | Google provider |\n| `compat-opentofu` | OpenTofu | Google provider |\n\nRun the full suite:\n\n```bash\ncd compatibility-tests \u0026\u0026 just test-java\ncd compatibility-tests \u0026\u0026 just test-go\ncd compatibility-tests \u0026\u0026 just test-terraform\n```\n\n## Image Tags\n\nEvery tag combines a variant and a channel.\n\n| Channel | Tag |\n|---|---|\n| Release, floating | `latest` |\n| Release, pinned | `x.y.z` |\n| Nightly, floating | `nightly` |\n| Nightly, dated | `nightly-mmddyyyy` |\n\nUse `latest` for stable releases, a pinned version for reproducible builds, and `nightly` to track `main`.\n\n```yaml\n# Recommended\nimage: floci/floci-gcp:latest\n\n# Pinned release\nimage: floci/floci-gcp:1.0.0\n\n# Track main\nimage: floci/floci-gcp:nightly\n```\n\n## Configuration\n\nAll settings are overridable via environment variables (`FLOCI_GCP_` prefix).\n\n| Variable | Default | Description |\n|---|---|---|\n| `FLOCI_GCP_PORT` | `4588` | Port for all services (gRPC + REST) |\n| `FLOCI_GCP_DEFAULT_PROJECT_ID` | `floci-local` | Default GCP project ID |\n| `FLOCI_GCP_BASE_URL` | `http://localhost:4588` | Base URL returned in service responses |\n| `FLOCI_GCP_HOSTNAME` | *(unset)* | Hostname to use in returned URLs when running inside Docker Compose |\n| `FLOCI_GCP_STORAGE_MODE` | `memory` | Storage mode: `memory` · `persistent` · `hybrid` · `wal` |\n| `FLOCI_GCP_STORAGE_PERSISTENT_PATH` | `./data` | Directory for persisted state |\n| `FLOCI_GCP_SERVICES_GCS_ENABLED` | `true` | Enable/disable Cloud Storage |\n| `FLOCI_GCP_SERVICES_PUBSUB_ENABLED` | `true` | Enable/disable Pub/Sub |\n| `FLOCI_GCP_SERVICES_FIRESTORE_ENABLED` | `true` | Enable/disable Firestore |\n| `FLOCI_GCP_SERVICES_DATASTORE_ENABLED` | `true` | Enable/disable Datastore |\n| `FLOCI_GCP_SERVICES_IAM_ENABLED` | `true` | Enable/disable IAM |\n| `FLOCI_GCP_SERVICES_SECRETMANAGER_ENABLED` | `true` | Enable/disable Secret Manager |\n| `FLOCI_GCP_SERVICES_KAFKA_ENABLED` | `true` | Enable/disable Managed Kafka |\n| `FLOCI_GCP_SERVICES_KAFKA_MOCK` | `false` | Use mock mode (no Docker; returns `ACTIVE` immediately) |\n| `FLOCI_GCP_DNS_EXTRA_SUFFIXES` | *(unset)* | Extra DNS suffixes for embedded DNS (comma-separated) |\n\n### Multi-container Docker Compose\n\nWhen your application runs in a different container, set `FLOCI_GCP_HOSTNAME` to the floci-gcp service name so returned URLs resolve correctly from other containers.\n\n```yaml\nservices:\n  floci-gcp:\n    image: floci/floci-gcp:latest\n    ports:\n      - \"4588:4588\"\n    environment:\n      FLOCI_GCP_HOSTNAME: floci-gcp\n      FLOCI_GCP_BASE_URL: http://floci-gcp:4588\n\n  my-app:\n    environment:\n      PUBSUB_EMULATOR_HOST: floci-gcp:4588\n      FIRESTORE_EMULATOR_HOST: floci-gcp:4588\n      STORAGE_EMULATOR_HOST: http://floci-gcp:4588\n    depends_on:\n      - floci-gcp\n```\n\n---\n\n## Contributors\n\n\u003ca href=\"https://github.com/floci-io/floci-gcp/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=hectorvent/floci-gcp\" /\u003e\n\u003c/a\u003e\n\n---\n\n## License\n\nMIT — use it however you want.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloci-io%2Ffloci-gcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffloci-io%2Ffloci-gcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloci-io%2Ffloci-gcp/lists"}