Deploy SupportPilot to Google Cloud
Cloud Run runs the container serverlessly (scale to zero, pay per request), Cloud SQL stores LangGraph checkpoints, Secret Manager holds keys, and GitHub Actions ships every push.
Your progress
Prerequisites
- A GCP project with billing enabled and the gcloud CLI installed.
- The SupportPilot code and its
Dockerfile. - An LLM API key (or use Vertex AI models — see below).
Step-by-step
1
Set variables and enable APIs
export PROJECT_ID=my-agent-project
export REGION=us-central1
gcloud auth login
gcloud config set project $PROJECT_ID
gcloud services enable run.googleapis.com artifactregistry.googleapis.com \
cloudbuild.googleapis.com sqladmin.googleapis.com secretmanager.googleapis.com \
iamcredentials.googleapis.com
2
Build and push the image
Cloud Build builds remotely — no local Docker needed and the image is always linux/amd64.
gcloud artifacts repositories create agents \
--repository-format=docker --location=$REGION
export IMAGE=$REGION-docker.pkg.dev/$PROJECT_ID/agents/supportpilot:v1
gcloud builds submit --tag $IMAGE .
3
Create Cloud SQL for Postgres
export DB_PASS=$(openssl rand -hex 16) # hex → no URL-encoding surprises
gcloud sql instances create supportpilot-db \
--database-version=POSTGRES_16 --edition=enterprise \
--tier=db-custom-1-3840 --region=$REGION
gcloud sql databases create agent --instance=supportpilot-db
gcloud sql users create agent --instance=supportpilot-db --password="$DB_PASS"
export INSTANCE_CONN=$(gcloud sql instances describe supportpilot-db \
--format='value(connectionName)') # project:region:instance
How Cloud Run connectsWith
--add-cloudsql-instances, Cloud Run mounts a Unix socket at /cloudsql/INSTANCE_CONN. libpq/psycopg accept it via the host query parameter, so no IP allow-listing is needed.4
Store secrets in Secret Manager
printf '%s' "$ANTHROPIC_API_KEY" | gcloud secrets create anthropic-api-key --data-file=-
printf '%s' "postgresql://agent:${DB_PASS}@/agent?host=/cloudsql/${INSTANCE_CONN}" \
| gcloud secrets create database-url --data-file=-
5
Create a least-privilege service account
gcloud iam service-accounts create supportpilot-run
export SA=supportpilot-run@$PROJECT_ID.iam.gserviceaccount.com
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:$SA --role=roles/cloudsql.client
for s in anthropic-api-key database-url; do
gcloud secrets add-iam-policy-binding $s \
--member=serviceAccount:$SA --role=roles/secretmanager.secretAccessor
done
6
Deploy to Cloud Run
gcloud run deploy supportpilot \
--image=$IMAGE --region=$REGION \
--service-account=$SA \
--add-cloudsql-instances=$INSTANCE_CONN \
--set-secrets=ANTHROPIC_API_KEY=anthropic-api-key:latest,DATABASE_URL=database-url:latest \
--set-env-vars=MODEL=anthropic:claude-sonnet-5-5 \
--cpu=1 --memory=1Gi --concurrency=40 --timeout=300 \
--min-instances=0 --max-instances=10 \
--no-allow-unauthenticated
--concurrency=40: agent requests are I/O-bound (waiting on the LLM), so one instance handles many.--timeout=300: long agent runs and SSE streams need more than the default.--min-instances=1removes cold starts for production (costs a bit when idle).--no-allow-unauthenticated: callers need an identity token. Put API Gateway / IAP / your backend in front for public use.
7
Smoke test
URL=$(gcloud run services describe supportpilot --region=$REGION --format='value(status.url)')
TOKEN=$(gcloud auth print-identity-token)
curl -s -H "Authorization: Bearer $TOKEN" $URL/healthz
curl -s -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
$URL/chat -d '{"thread_id":"gcp-1","message":"Order A1002 is late, refund it"}'
curl -s -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
$URL/approve -d '{"thread_id":"gcp-1","approved":true}'
gcloud run services logs read supportpilot --region=$REGION --limit=50
8
CI/CD with GitHub Actions (keyless)
Workload Identity Federation lets GitHub authenticate to GCP with short-lived OIDC tokens — no JSON keys in GitHub secrets.
gcloud iam service-accounts create github-deployer
export DEPLOYER=github-deployer@$PROJECT_ID.iam.gserviceaccount.com
for r in roles/run.admin roles/artifactregistry.writer roles/cloudbuild.builds.editor roles/iam.serviceAccountUser; do
gcloud projects add-iam-policy-binding $PROJECT_ID --member=serviceAccount:$DEPLOYER --role=$r
done
gcloud iam workload-identity-pools create github --location=global
gcloud iam workload-identity-pools providers create-oidc github \
--location=global --workload-identity-pool=github \
--issuer-uri=https://token.actions.githubusercontent.com \
--attribute-mapping=google.subject=assertion.sub,attribute.repository=assertion.repository \
--attribute-condition="assertion.repository=='YOUR_ORG/YOUR_REPO'"
export POOL=$(gcloud iam workload-identity-pools describe github --location=global --format='value(name)')
gcloud iam service-accounts add-iam-policy-binding $DEPLOYER \
--role=roles/iam.workloadIdentityUser \
--member="principalSet://iam.googleapis.com/$POOL/attribute.repository/YOUR_ORG/YOUR_REPO"
name: deploy-gcp
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
env:
REGION: us-central1
IMAGE: us-central1-docker.pkg.dev/${{ vars.GCP_PROJECT }}/agents/supportpilot:${{ github.sha }}
jobs:
test-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -r requirements-dev.txt && pytest -q
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
service_account: ${{ vars.GCP_DEPLOYER_SA }}
- uses: google-github-actions/setup-gcloud@v2
- run: gcloud builds submit --tag "$IMAGE" .
- uses: google-github-actions/deploy-cloudrun@v2
with:
service: supportpilot
region: ${{ env.REGION }}
image: ${{ env.IMAGE }}
Only the image changes on each deploy; secrets, Cloud SQL and the service account set in step 6 are kept.
Using Vertex AI models instead of an API key
On GCP you can call Gemini (and partner models such as Claude) through Vertex AI, authenticated by the service account — no API key to rotate.
# requirements.txt: add langchain-google-vertexai
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:$SA --role=roles/aiplatform.user
gcloud run services update supportpilot --region=$REGION \
--update-env-vars=MODEL=google_vertexai:YOUR_GEMINI_MODEL_ID,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=$REGION \
--remove-secrets=ANTHROPIC_API_KEY
Because the graph uses init_chat_model(settings.model), nothing else changes.
Alternatives on GCP
| Option | When to choose it |
|---|---|
| Cloud Run (this guide) | Default for HTTP agent APIs: serverless, scales to zero, simple. |
| Vertex AI Agent Engine | Managed runtime for agents (LangGraph, ADK) with managed sessions and memory; less infra to own. |
| GKE (Autopilot) | You already run Kubernetes, need GPUs for self-hosted models, or sidecars/custom networking. |
| Cloud Run jobs | Batch agents (nightly ticket triage) and one-off DB migrations. |
Production notes & cost
- Networking: for private-IP Cloud SQL, use Direct VPC egress and the Cloud SQL Auth Proxy/connector.
- Observability: logs go to Cloud Logging automatically; export OpenTelemetry traces to Cloud Trace, or use LangSmith.
- Rollouts:
gcloud run deploy --no-traffic --tag canarythen shift traffic gradually withupdate-traffic. - Cost drivers: LLM tokens usually dominate; then Cloud SQL (always on). Cloud Run at low traffic is often within the free tier.
# clean up
gcloud run services delete supportpilot --region=$REGION
gcloud sql instances delete supportpilot-db
gcloud artifacts repositories delete agents --location=$REGION
gcloud secrets delete anthropic-api-key; gcloud secrets delete database-url