| 1 | --- |
| 2 | title: GCP |
| 3 | description: Google Cloud in one Effect program — Cloud Run services and jobs, Firestore, Pub/Sub, BigQuery, Secret Manager, and Memorystore, with IAM granted by the bindings themselves. |
| 4 | --- |
| 5 | |
| 6 | GCP with alchemy means the Cloud Run service and everything it talks |
| 7 | to live in the same TypeScript program. The Firestore database is |
| 8 | declared on the service that reads it. The service calls Google APIs |
| 9 | through typed bindings that carry their own IAM. `bun alchemy deploy` |
| 10 | builds the container, mints a runtime service account, grants exactly |
| 11 | the roles the bindings need, and ships it; `alchemy destroy` takes all |
| 12 | of that back down, service account included. |
| 13 | |
| 14 | New here? [Set up credentials](/gcp/setup), then pick the guide that |
| 15 | matches what you're building. |
| 16 | |
| 17 | ## What are you building? |
| 18 | |
| 19 | - **An HTTP API with a datastore and a secret** → |
| 20 | [Serve an API on Cloud Run](/gcp/guides/cloud-run-api). A link |
| 21 | shortener on one Cloud Run service, Firestore for state, Secret |
| 22 | Manager for the API key. |
| 23 | - **An ingestion pipeline** → |
| 24 | [Ingest events into BigQuery](/gcp/guides/event-pipeline). A service |
| 25 | publishes to Pub/Sub, a Cloud Run Job drains the subscription into |
| 26 | BigQuery, and the service triggers the job. |
| 27 | - **Something that needs a cache** → |
| 28 | [Cache with Memorystore](/gcp/guides/memorystore). Redis on a |
| 29 | private IP, reached over Direct VPC egress. |
| 30 | - **Wondering where the IAM comes from** → |
| 31 | [How bindings grant IAM](/gcp/guides/bindings). |
| 32 | |
| 33 | Each guide walks a runnable example under `examples/` end to end. |
| 34 | |
| 35 | ## What it looks like |
| 36 | |
| 37 | ```typescript |
| 38 | // src/Api.ts |
| 39 | export default class Api extends GCP.Function<Api>()( |
| 40 | "Api", |
| 41 | { main: import.meta.url, location: "us-central1" }, |
| 42 | Effect.gen(function* () { |
| 43 | // Resources — deployed with the service. |
| 44 | const links = yield* GCP.Firestore.Database("Links", { |
| 45 | location: "us-central1", |
| 46 | type: "FIRESTORE_NATIVE", |
| 47 | }); |
| 48 | |
| 49 | // A binding grants its role on the runtime service account. |
| 50 | const db = yield* GCP.Firestore.ReadWriteDatabase(links); |
| 51 | |
| 52 | return { |
| 53 | fetch: Effect.gen(function* () { |
| 54 | yield* db.set("links/demo", { url: "https://alchemy.run" }); |
| 55 | const link = yield* db.get("links/demo"); |
| 56 | return yield* HttpServerResponse.json(link?.fields ?? {}); |
| 57 | }).pipe(Effect.orDie), |
| 58 | }; |
| 59 | }).pipe(Effect.provide(GCP.Firestore.ReadWriteDatabaseHttp)), |
| 60 | ) {} |
| 61 | ``` |
| 62 | |
| 63 | Three ideas carry the whole provider: |
| 64 | |
| 65 | - **Resources** (`Firestore.Database`, `PubSub.Topic`, |
| 66 | `BigQuery.Table`, `SecretManager.Secret`, `Redis.Instance`, …) are |
| 67 | Stack-managed. Alchemy creates, updates, and deletes them. |
| 68 | - **Bindings** (`ReadWriteDatabase`, `WriteTopic`, `ReadTable`, |
| 69 | `ReadSecret`, `RunJob`, …) are what the container calls at |
| 70 | runtime. Yielding one grants the IAM role it needs on a per-host |
| 71 | service account and returns a callable. Provide the matching `*Http` |
| 72 | layer once at the end. |
| 73 | - **Outputs** like `table.tableId` are yielded at plan time to get an |
| 74 | accessor, then yielded again where the value is needed. That's how a |
| 75 | resource id reaches runtime without an environment variable. |
| 76 | |
| 77 | ## Hosts |
| 78 | |
| 79 | | Host | Entry | Use it for | |
| 80 | | --- | --- | --- | |
| 81 | | `GCP.Function` (= `GCP.Run.Service`) | `fetch` | HTTP services that scale to zero | |
| 82 | | `GCP.Run.Job` | `run` | batch work that starts, finishes, and exits | |
| 83 | | `GCP.Run.WorkerPool` | `run` | always-on consumers with no request path | |
| 84 | | `GCP.CloudFunctions.Function` | `fetch` | gen2 functions built in the cloud (Node.js) | |
| 85 | |
| 86 | The Run hosts build a container image locally, so Docker has to be |
| 87 | running to deploy them. Cloud Functions build from an uploaded archive. |
| 88 | |
| 89 | ## Event sources |
| 90 | |
| 91 | Google calls your code through an event source: `consumeTopicMessages` |
| 92 | (Pub/Sub push or pull), `consumeBucketEvents` (Cloud Storage), |
| 93 | `consumeSchedule` (Cloud Scheduler), and `consumeEvents` (Eventarc). |
| 94 | See [How bindings grant IAM](/gcp/guides/bindings#event-sources). |
| 95 | |
| 96 | ## Examples |
| 97 | |
| 98 | Every example deploys, is exercised by a live integration test, and |
| 99 | tears itself down. |
| 100 | |
| 101 | | Example | What it shows | |
| 102 | | --- | --- | |
| 103 | | `gcp-cloud-run-api` | HTTP API on Cloud Run with Firestore and Secret Manager | |
| 104 | | `gcp-cloud-function` | Effect-native Cloud Function (Node 22) over Firestore | |
| 105 | | `gcp-event-pipeline` | Service → Pub/Sub → Cloud Run Job → BigQuery | |
| 106 | | `gcp-pubsub-worker` | Job queue: API publishes, a WorkerPool pulls | |
| 107 | | `gcp-pubsub-fanout` | Push fan-out to two services, attribute filter, dead-letter topic | |
| 108 | | `gcp-storage-uploads` | Upload API plus a bucket-event indexer | |
| 109 | | `gcp-cron` | Two Cloud Scheduler crons on one service, rows in BigQuery | |
| 110 | | `gcp-scheduled-job` | Nightly Cloud Run Job from Cloud Scheduler and an admin route | |
| 111 | | `gcp-eventarc-firestore` | Eventarc reacting to Firestore document creation | |
| 112 | | `gcp-secrets-kms` | Encryption API with KMS and a Secret Manager API key | |
| 113 | | `gcp-service-to-service` | Private service called with Google-signed identity (`InvokeService`) | |
| 114 | | `gcp-cloud-sql-drizzle` | Drizzle over Cloud SQL Postgres via the Cloud Run socket | |
| 115 | | `gcp-memorystore-redis` | Rate limiter on Memorystore over Direct VPC | |
| 116 | | `gcp-gke` | Guestbook on GKE Autopilot: Deployments, a seed Job, Firestore via Workload Identity | |
| 117 | | `gcp-vertex-ai` | Gemini on Vertex AI (`GenerateContent`) | |
| 118 | | `gcp-static-site` | Static site from a public Cloud Storage bucket | |
| 119 | |
| 120 | ## Reference |
| 121 | |
| 122 | [Run.Service](/providers/gcp/run/service) · |
| 123 | [Run.Job](/providers/gcp/run/job) · |
| 124 | [Firestore.Database](/providers/gcp/firestore/database) · |
| 125 | [PubSub.Topic](/providers/gcp/pubsub/topic) · |
| 126 | [BigQuery.Table](/providers/gcp/bigquery/table) · |
| 127 | [SecretManager.Secret](/providers/gcp/secretmanager/secret) · |
| 128 | [Storage.Bucket](/providers/gcp/storage/bucket) · |
| 129 | [Redis.Instance](/providers/gcp/redis/instance) |
| 130 | |
| 131 | The full list is under **Resources** in the sidebar. |
| 132 | |
| 133 | ## Out of scope |
| 134 | |
| 135 | Alchemy manages infrastructure, not end-user content. APIs whose objects |
| 136 | are a person's data — Gmail messages and settings, Forms, Calendar events, Drive files, |
| 137 | Classroom courses, Chat messages, Tasks, Keep notes, People contacts, |
| 138 | Blogger posts, YouTube videos and reports, Fitness data, Apps Script |
| 139 | projects, Street View photos, Fact Check claims, and Business Profile |
| 140 | listings — are deliberately not modelled as resources. Neither is |
| 141 | `prodtt-sasportal`, a test-environment copy of the SAS Portal API. |
| 142 | |
| 143 | The advertising and marketing APIs — Analytics Admin, Display & Video 360, |
| 144 | Tag Manager, Workspace Reseller, AdSense, AdSense Platform, Ad Exchange Buyer |
| 145 | II, Campaign Manager 360 (DFA Reporting), and DoubleClick Bid Manager — |
| 146 | are also out of scope: they act on a user's accounts and require user OAuth |
| 147 | scopes, which a service account or Workload Identity host cannot hold. The |
| 148 | Content API for Shopping and Apigee Registry are retired by Google and are not |
| 149 | modelled. |