Follow Discord
Sweep 06 Nov 2026 · 01:32Z Build v2.1.319 537 read Stable v2.1.311 Latest v2.1.318 Next v2.1.319 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Deploy Claude apps gateway on Google Cloud changedclaude-apps-gateway-on-gcp

Nearest release: v2.1.307, published 3 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 21 Oct 2026 23:58 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 30 Oct 2026 18:07 UTC.

Upstream edited
Recorded here
Lines+79added
Lines−79removed
From line 199 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits10to this page, all time

The whole hunk

from line 199, old and new numbered
/
lines
from line 199
199199 
200200**7. Deploy**
201201 
202**Cloud Run**
203 
204The command below deploys for production behind an internal load balancer.
205 
206```bash theme={null}
207gcloud run deploy claude-gateway \
208 --image="${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" \
209 --region="$REGION" \
210 --service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
211 --min-instances=1 \
212 --max-instances=8 \
213 --timeout=3600 \
214 --ingress=internal \
215 --network="$VPC" --subnet=cc-gateway-subnet --vpc-egress=private-ranges-only \
216 --set-secrets=/etc/claude/gateway.yaml=gateway-config:latest,GATEWAY_JWT_SECRET=gateway-jwt-secret:latest,OIDC_CLIENT_SECRET=gateway-oidc-client-secret:latest,GATEWAY_POSTGRES_URL=gateway-postgres-url:latest \
217 --no-invoker-iam-check
218```
219 
220Direct VPC egress, via `--network`, `--subnet`, and `--vpc-egress=private-ranges-only`, lets the service reach the Cloud SQL private IP directly. Public egress to Google Cloud's Agent Platform endpoints and `accounts.google.com` goes directly to the internet rather than through the VPC, so no Cloud NAT is needed.
221 
222> [!NOTE]
202> [tab] Cloud Run
223203>
224> Each instance holds up to [`store.max_connections`](https://code.claude.com/docs/en/claude-apps-gateway-config#store) Postgres connections, five by default. Use [Size the connection pool](https://code.claude.com/docs/en/claude-apps-gateway-deploy.md#size-the-connection-pool) to check `--max-instances=8` against the connections your database has left for the gateway, and include the extra instances a rolling update starts.
204> The command below deploys for production behind an internal load balancer.
205>
206> ```bash theme={null}
207> gcloud run deploy claude-gateway \
208> --image="${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" \
209> --region="$REGION" \
210> --service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
211> --min-instances=1 \
212> --max-instances=8 \
213> --timeout=3600 \
214> --ingress=internal \
215> --network="$VPC" --subnet=cc-gateway-subnet --vpc-egress=private-ranges-only \
216> --set-secrets=/etc/claude/gateway.yaml=gateway-config:latest,GATEWAY_JWT_SECRET=gateway-jwt-secret:latest,OIDC_CLIENT_SECRET=gateway-oidc-client-secret:latest,GATEWAY_POSTGRES_URL=gateway-postgres-url:latest \
217> --no-invoker-iam-check
218> ```
219>
220> Direct VPC egress, via `--network`, `--subnet`, and `--vpc-egress=private-ranges-only`, lets the service reach the Cloud SQL private IP directly. Public egress to Google Cloud's Agent Platform endpoints and `accounts.google.com` goes directly to the internet rather than through the VPC, so no Cloud NAT is needed.
221>
222> > [!NOTE]
223> >
224> > Each instance holds up to [`store.max_connections`](https://code.claude.com/docs/en/claude-apps-gateway-config#store) Postgres connections, five by default. Use [Size the connection pool](https://code.claude.com/docs/en/claude-apps-gateway-deploy.md#size-the-connection-pool) to check `--max-instances=8` against the connections your database has left for the gateway, and include the extra instances a rolling update starts.
225>
226> The invoker IAM check must be open or disabled. The gateway runs its own OIDC and its clients carry no GCP token, so Cloud Run's invoker check has to admit unauthenticated requests. The gateway's OIDC sign-in authenticates the request once it reaches the container, with `allowed_email_domains` gating which domains may sign in.
227>
228> Two flags admit unauthenticated requests:
229>
230> * `--no-invoker-iam-check`: disables the check with no `allUsers` binding to manage, and works under Domain Restricted Sharing
231> * `--allow-unauthenticated`: grants `allUsers` the `run.invoker` role; use it if your organization doesn't allow `--no-invoker-iam-check`
232>
233> Ingress restriction via `--ingress` is a separate, independent layer from the invoker check; keep it set to limit the service to your corporate network.
234>
235> By default the Cloud Run `*.run.app` URL resolves to a public address, which the `/login` [private-network check](https://code.claude.com/docs/en/claude-apps-gateway#prerequisites) rejects. Two topologies give developers a privately resolvable hostname, and Cloud Run provisions neither for you:
236>
237> * **Internal Application Load Balancer**, the topology this page's `gateway.yaml` assumes: provision an internal Application Load Balancer in front of the service with an internal DNS name and certificate, and set `listen.public_url` to that hostname. The `internal` ingress setting already admits traffic from internal Application Load Balancers; `internal-and-cloud-load-balancing` additionally admits external Application Load Balancers, whose public addresses the `/login` private-network check rejects, so no topology on this page needs it.
238> * **Internal-only ingress with no load balancer**: keep the deploy command as is and leave `listen.public_url` as the `*.run.app` URL, the default in the [reference assets](#terraform-reference) below. For `*.run.app` to resolve privately, your network team must already operate a Private Service Connect endpoint for Google APIs, a Cloud DNS private zone resolving `*.run.app` to it, and on-premises routing to that endpoint.
239>
240> Google's [private networking guide for Cloud Run](https://cloud.google.com/run/docs/securing/private-networking) covers the infrastructure both options need. Verify sign-in once the gateway is serving on a private hostname; until then, confirm the container booted from its logs in Cloud Run.
241>
242> Update the OAuth client's authorized redirect URI to `<public_url>/oauth/callback` before the first sign-in. Redeploy after changing `public_url`, because the gateway builds its public origin only from that setting and ignores `X-Forwarded-Host` and `X-Forwarded-Proto`. `X-Forwarded-For` is honored for client IPs only when `listen.trusted_proxies` is set.
225243 
226The invoker IAM check must be open or disabled. The gateway runs its own OIDC and its clients carry no GCP token, so Cloud Run's invoker check has to admit unauthenticated requests. The gateway's OIDC sign-in authenticates the request once it reaches the container, with `allowed_email_domains` gating which domains may sign in.
227 
228Two flags admit unauthenticated requests:
229 
230* `--no-invoker-iam-check`: disables the check with no `allUsers` binding to manage, and works under Domain Restricted Sharing
231* `--allow-unauthenticated`: grants `allUsers` the `run.invoker` role; use it if your organization doesn't allow `--no-invoker-iam-check`
232 
233Ingress restriction via `--ingress` is a separate, independent layer from the invoker check; keep it set to limit the service to your corporate network.
234 
235By default the Cloud Run `*.run.app` URL resolves to a public address, which the `/login` [private-network check](https://code.claude.com/docs/en/claude-apps-gateway#prerequisites) rejects. Two topologies give developers a privately resolvable hostname, and Cloud Run provisions neither for you:
236 
237* **Internal Application Load Balancer**, the topology this page's `gateway.yaml` assumes: provision an internal Application Load Balancer in front of the service with an internal DNS name and certificate, and set `listen.public_url` to that hostname. The `internal` ingress setting already admits traffic from internal Application Load Balancers; `internal-and-cloud-load-balancing` additionally admits external Application Load Balancers, whose public addresses the `/login` private-network check rejects, so no topology on this page needs it.
238* **Internal-only ingress with no load balancer**: keep the deploy command as is and leave `listen.public_url` as the `*.run.app` URL, the default in the [reference assets](#terraform-reference) below. For `*.run.app` to resolve privately, your network team must already operate a Private Service Connect endpoint for Google APIs, a Cloud DNS private zone resolving `*.run.app` to it, and on-premises routing to that endpoint.
239 
240Google's [private networking guide for Cloud Run](https://cloud.google.com/run/docs/securing/private-networking) covers the infrastructure both options need. Verify sign-in once the gateway is serving on a private hostname; until then, confirm the container booted from its logs in Cloud Run.
241 
242Update the OAuth client's authorized redirect URI to `<public_url>/oauth/callback` before the first sign-in. Redeploy after changing `public_url`, because the gateway builds its public origin only from that setting and ignores `X-Forwarded-Host` and `X-Forwarded-Proto`. `X-Forwarded-For` is honored for client IPs only when `listen.trusted_proxies` is set.
243 
244**GKE**
245 
246The cluster must be on the `$VPC` created in the Cloud SQL step so pods can reach the database's private IP; VPC peering alone doesn't work, because Cloud SQL private IP is itself a peered network and peering is non-transitive. To create a new cluster on that VPC, pass `--network="$VPC" --subnetwork=cc-gateway-subnet` to `gcloud container clusters create`.
247 
248Enable Workload Identity on the cluster and its node pools, then bind the Google service account to the Kubernetes service account so pods inherit its credentials:
249 
250```bash theme={null}
251gcloud container clusters update <cluster> --region="$REGION" \
252 --workload-pool="${PROJECT_ID}.svc.id.goog"
253# On a Standard cluster, existing node pools also need GKE_METADATA;
254# Autopilot enables this by default.
255gcloud container node-pools update <pool> --cluster=<cluster> \
256 --region="$REGION" --workload-metadata=GKE_METADATA
257 
258kubectl create namespace claude-gateway
259kubectl create serviceaccount gateway -n claude-gateway
260 
261gcloud iam service-accounts add-iam-policy-binding \
262 "claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
263 --role roles/iam.workloadIdentityUser \
264 --member "serviceAccount:${PROJECT_ID}.svc.id.goog[claude-gateway/gateway]"
265 
266kubectl annotate serviceaccount gateway -n claude-gateway \
267 iam.gke.io/gcp-service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"
268```
269 
270Deploy the gateway as a standard Deployment plus a Service and an internal Ingress, class `gce-internal`, as described in [Kubernetes deployment](https://code.claude.com/docs/en/claude-apps-gateway-deploy.md#kubernetes), with:
271 
272* `serviceAccountName: gateway`
273* the Secret Manager CSI driver mounting secrets at `/secrets`
274* the readiness probe pointed at `GET /readyz`
275 
276Attach a BackendConfig with a raised `timeoutSec` to the gateway Service: the load balancer backend service behind GKE Ingress defaults to a 30-second timeout, which cuts off long streaming responses.
277 
278Don't apply an egress NetworkPolicy that blocks `169.254.169.254` on a Workload Identity cluster; the pod must reach the metadata server for credentials. The gateway's built-in [SSRF guard](https://code.claude.com/docs/en/claude-apps-gateway-deploy.md#threat-model-summary) is the defense there.
279 
280The gateway logs a boot warning that the metadata endpoint is reachable and suggests applying an egress NetworkPolicy. Under Workload Identity that warning is expected, because the pod needs the endpoint.
244> [tab] GKE
245>
246> The cluster must be on the `$VPC` created in the Cloud SQL step so pods can reach the database's private IP; VPC peering alone doesn't work, because Cloud SQL private IP is itself a peered network and peering is non-transitive. To create a new cluster on that VPC, pass `--network="$VPC" --subnetwork=cc-gateway-subnet` to `gcloud container clusters create`.
247>
248> Enable Workload Identity on the cluster and its node pools, then bind the Google service account to the Kubernetes service account so pods inherit its credentials:
249>
250> ```bash theme={null}
251> gcloud container clusters update <cluster> --region="$REGION" \
252> --workload-pool="${PROJECT_ID}.svc.id.goog"
253> # On a Standard cluster, existing node pools also need GKE_METADATA;
254> # Autopilot enables this by default.
255> gcloud container node-pools update <pool> --cluster=<cluster> \
256> --region="$REGION" --workload-metadata=GKE_METADATA
257>
258> kubectl create namespace claude-gateway
259> kubectl create serviceaccount gateway -n claude-gateway
260>
261> gcloud iam service-accounts add-iam-policy-binding \
262> "claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
263> --role roles/iam.workloadIdentityUser \
264> --member "serviceAccount:${PROJECT_ID}.svc.id.goog[claude-gateway/gateway]"
265>
266> kubectl annotate serviceaccount gateway -n claude-gateway \
267> iam.gke.io/gcp-service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"
268> ```
269>
270> Deploy the gateway as a standard Deployment plus a Service and an internal Ingress, class `gce-internal`, as described in [Kubernetes deployment](https://code.claude.com/docs/en/claude-apps-gateway-deploy.md#kubernetes), with:
271>
272> * `serviceAccountName: gateway`
273> * the Secret Manager CSI driver mounting secrets at `/secrets`
274> * the readiness probe pointed at `GET /readyz`
275>
276> Attach a BackendConfig with a raised `timeoutSec` to the gateway Service: the load balancer backend service behind GKE Ingress defaults to a 30-second timeout, which cuts off long streaming responses.
277>
278> Don't apply an egress NetworkPolicy that blocks `169.254.169.254` on a Workload Identity cluster; the pod must reach the metadata server for credentials. The gateway's built-in [SSRF guard](https://code.claude.com/docs/en/claude-apps-gateway-deploy.md#threat-model-summary) is the defense there.
279>
280> The gateway logs a boot warning that the metadata endpoint is reachable and suggests applying an egress NetworkPolicy. Under Workload Identity that warning is expected, because the pod needs the endpoint.
281281 
282282**8. Push the gateway URL to developer machines**
283283 
Feedback