Use Cases (Updated: 6/3/2026)

GCP Cloud Functions with Claude Code: HTTP, Secret Manager, Cloud Logging

Build Cloud Run functions with Claude Code: HTTP handlers, Secret Manager, Cloud Logging, deploy checks, and pitfalls.

GCP Cloud Functions with Claude Code: HTTP, Secret Manager, Cloud Logging

Google Cloud documentation now often presents Cloud Functions through the Cloud Run functions path. The practical model is simple: you deploy source code, Google Cloud builds it into a container, and the result runs as a Cloud Run service that is invoked by HTTP requests or events.

This guide upgrades the Claude Code workflow around GCP Cloud Functions: one Node.js HTTP function, one Cloud Storage event function through Eventarc, Secret Manager, Cloud Logging, and deployment verification. Eventarc is Google Cloud’s event delivery layer. Functions Framework is the small adapter that lets the same function shape run locally and in the cloud.

My rule at ClaudeCodeLab is to use Cloud Run functions when the unit of work is naturally small: a webhook receiver, a lightweight notification bridge, a CSV import trigger, or a scheduled job entry point. I choose Cloud Run instead when I need many routes, a full API, Next.js or Express, WebSockets, a custom Dockerfile, long-running work, or detailed container control. The related GCP Cloud Run guide covers that side.

Claude Code is useful for generating the repeatable parts: package.json, Functions Framework registration, curl commands, gcloud run deploy, IAM notes, and a review prompt. Human review still matters for authentication, Secret Manager, retries, idempotency, and logs. Idempotency means receiving the same request or event twice does not corrupt the result.


Good Use Cases

Use caseEntry pointWhat to review
Stripe, GitHub, or internal webhooksHTTP functionSignature checks, Bearer token, replay handling, Secret Manager
CSV import after Cloud Storage uploadEventarc + CloudEvent functionDuplicate events, bucket location, file naming rules
Contact form notificationHTTP function or Pub/Sub handoffFast 200 response, queue handoff, rate limits
Nightly sync or report triggerCloud Scheduler + HTTP functionOIDC authentication, time zone, timeout

These cases work well with Claude Code because the input, validation, logging, and failure behavior are easy to turn into a checklist. The design becomes fragile when one function owns many unrelated jobs, runs long loops, or acts as an entire user-facing API.

For team processes, pair this article with Claude Code code review workflows and secret management with Claude Code.


Minimal Project

This example uses CommonJS Node.js so it can run without a build step. The official docs use Functions Framework to register HTTP and CloudEvent functions, and the same framework works locally.

functions-demo/
  index.js
  package.json
{
  "name": "claude-code-gcp-functions-demo",
  "version": "1.0.0",
  "private": true,
  "main": "index.js",
  "scripts": {
    "start:http": "functions-framework --target=handleAction --port=8080",
    "start:event": "functions-framework --target=handleStorageObject --signature-type=cloudevent --port=8081"
  },
  "dependencies": {
    "@google-cloud/firestore": "^7.11.0",
    "@google-cloud/functions-framework": "^3.4.6"
  }
}
npm install

Function Code

Save this as index.js. The HTTP function checks Authorization: Bearer ... and uses Idempotency-Key as the duplicate-prevention key. The Storage event function stores the CloudEvent ID in Firestore so a retried event does not create the same job twice.

const functions = require("@google-cloud/functions-framework");
const { Firestore } = require("@google-cloud/firestore");
const crypto = require("node:crypto");

const db = new Firestore();

function jsonLog(severity, message, extra = {}) {
  console.log(JSON.stringify({ severity, message, ...extra }));
}

function requireBearerToken(req) {
  const expected = process.env.API_TOKEN;
  const header = req.get("Authorization") || "";
  return Boolean(expected && header === `Bearer ${expected}`);
}

function stableHash(value) {
  return crypto.createHash("sha256").update(value).digest("hex");
}

functions.http("handleAction", async (req, res) => {
  if (req.method !== "POST") {
    res.status(405).json({ ok: false, error: "POST only" });
    return;
  }

  if (!requireBearerToken(req)) {
    res.status(401).json({ ok: false, error: "invalid token" });
    return;
  }

  const body = req.body || {};
  if (typeof body.userId !== "string" || typeof body.action !== "string") {
    res.status(400).json({ ok: false, error: "userId and action are required" });
    return;
  }

  const idempotencyKey =
    req.get("Idempotency-Key") ||
    stableHash(`${body.userId}:${body.action}:${body.requestedAt || ""}`);

  const requestRef = db.collection("function_requests").doc(idempotencyKey);
  const logRef = db.collection("action_logs").doc(idempotencyKey);

  try {
    let duplicate = false;
    await db.runTransaction(async (tx) => {
      const existing = await tx.get(requestRef);
      if (existing.exists) {
        duplicate = true;
        return;
      }

      tx.create(requestRef, {
        userId: body.userId,
        action: body.action,
        createdAt: new Date(),
        source: "handleAction"
      });
      tx.set(logRef, {
        userId: body.userId,
        action: body.action,
        createdAt: new Date()
      });
    });

    jsonLog("INFO", "action accepted", { userId: body.userId, duplicate });
    res.status(200).json({ ok: true, duplicate, idempotencyKey });
  } catch (error) {
    jsonLog("ERROR", "action failed", { error: String(error) });
    res.status(500).json({ ok: false, error: "internal error" });
  }
});

functions.cloudEvent("handleStorageObject", async (cloudEvent) => {
  const data = cloudEvent.data || {};
  const bucket = data.bucket;
  const name = data.name;

  if (!bucket || !name) {
    jsonLog("WARNING", "storage event missing bucket or name", { eventId: cloudEvent.id });
    return;
  }

  const eventRef = db.collection("processed_storage_events").doc(cloudEvent.id);
  const jobRef = db.collection("storage_import_jobs").doc(stableHash(`${bucket}/${name}`));

  await db.runTransaction(async (tx) => {
    const existing = await tx.get(eventRef);
    if (existing.exists) {
      jsonLog("INFO", "duplicate storage event ignored", { eventId: cloudEvent.id });
      return;
    }

    tx.create(eventRef, {
      bucket,
      name,
      eventType: cloudEvent.type,
      createdAt: new Date()
    });
    tx.set(jobRef, {
      bucket,
      name,
      status: "queued",
      updatedAt: new Date()
    }, { merge: true });
  });

  jsonLog("INFO", "storage import job queued", { bucket, name, eventId: cloudEvent.id });
});

Structured logs make Cloud Logging usable during incidents. Cloud Run automatically sends stdout and stderr to Cloud Logging, so JSON lines with severity, message, eventId, and userId are much easier to filter than plain strings.


Local Checks

Start the HTTP function:

export API_TOKEN="local-token"
npm run start:http

Send a request from another terminal:

curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer local-token" \
  -H "Idempotency-Key: local-001" \
  -d '{"userId":"user-123","action":"login","requestedAt":"2026-06-03T00:00:00Z"}'

Start the CloudEvent function:

npm run start:event
curl -X POST http://localhost:8081 \
  -H "Content-Type: application/json" \
  -H "ce-id: local-event-001" \
  -H "ce-specversion: 1.0" \
  -H "ce-type: google.cloud.storage.object.v1.finalized" \
  -H "ce-source: //storage.googleapis.com/projects/_/buckets/demo-bucket" \
  -d '{"bucket":"demo-bucket","name":"inbox/sample.csv","metageneration":"1"}'

If the local code connects to Firestore, run gcloud auth application-default login or use a dedicated test project. Do not point smoke tests at production data.


Secrets and IAM

Do not put tokens in source code or .env files that get copied into reviews. Store the token in Secret Manager and grant the runtime service account access.

PROJECT_ID="$(gcloud config get-value project)"
REGION="asia-northeast1"
RUNTIME_SA="functions-runtime@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud iam service-accounts create functions-runtime \
  --display-name="Functions runtime service account"

printf "replace-with-real-token" | gcloud secrets create api-token \
  --replication-policy="automatic" \
  --data-file=-

gcloud secrets add-iam-policy-binding api-token \
  --member="serviceAccount:${RUNTIME_SA}" \
  --role="roles/secretmanager.secretAccessor"

gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
  --member="serviceAccount:${RUNTIME_SA}" \
  --role="roles/datastore.user"

Keep two identities separate. The deployer needs permission to create Cloud Run functions and act as the service account. The runtime service account needs only the permissions the function uses, such as Secret Manager access and Firestore writes.


Deploy With gcloud run

Current docs show Cloud Run functions deployed with gcloud run deploy. For a private HTTP endpoint, start authenticated by default:

gcloud run deploy handle-action \
  --source . \
  --function handleAction \
  --base-image nodejs24 \
  --region "${REGION}" \
  --service-account "${RUNTIME_SA}" \
  --no-allow-unauthenticated \
  --memory 512Mi \
  --timeout 60s \
  --max-instances 20

gcloud run services update handle-action \
  --region "${REGION}" \
  --update-secrets=API_TOKEN=api-token:latest

For the Storage event function, deploy the service first and then create an Eventarc trigger:

BUCKET="your-import-bucket"
EVENTARC_SA="eventarc-invoker@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud iam service-accounts create eventarc-invoker \
  --display-name="Eventarc trigger invoker"

gcloud run deploy storage-import \
  --source . \
  --function handleStorageObject \
  --base-image nodejs24 \
  --region "${REGION}" \
  --service-account "${RUNTIME_SA}" \
  --no-allow-unauthenticated \
  --memory 512Mi \
  --timeout 120s \
  --max-instances 10

gcloud run services add-iam-policy-binding storage-import \
  --region "${REGION}" \
  --member="serviceAccount:${EVENTARC_SA}" \
  --role="roles/run.invoker"

gcloud eventarc triggers create storage-finalized-to-function \
  --location="${REGION}" \
  --destination-run-service=storage-import \
  --destination-run-region="${REGION}" \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=${BUCKET}" \
  --service-account="${EVENTARC_SA}"

Eventarc triggers can take a few minutes to become active. Also check region alignment: the Cloud Storage bucket, Eventarc trigger, and Cloud Run service should be planned together. Region choices affect latency, data residency, and pricing tiers.


Logs and Verification

After deploy, test the endpoint and inspect logs before considering the function ready.

SERVICE_URL="$(gcloud run services describe handle-action \
  --region "${REGION}" \
  --format='value(status.url)')"

curl -X POST "${SERVICE_URL}" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer replace-with-real-token" \
  -H "Idempotency-Key: prod-smoke-001" \
  -d '{"userId":"smoke-user","action":"deploy-check","requestedAt":"2026-06-03T00:00:00Z"}'

gcloud run services logs read handle-action \
  --region "${REGION}" \
  --limit 20

gcloud logging read \
  'resource.type="cloud_run_revision" AND resource.labels.service_name="handle-action" AND jsonPayload.message="action accepted"' \
  --limit 20 \
  --format json

Log enough to diagnose incidents: event ID, file name, duplicate flag, external API status, and safe business IDs. Do not log tokens, full message bodies, or personal data.


Pitfalls

First, retries without idempotency create duplicate side effects. Billing, emails, inventory updates, and imports need a stored event ID or business key.

Second, Secret Manager access must be granted to the runtime service account. A deploy can succeed while production requests fail with Permission denied.

Third, public HTTP functions need more than --allow-unauthenticated. For public webhooks, add signature checks, rate limits, and an edge design such as API Gateway or Cloud Armor when appropriate. For internal jobs, prefer authenticated calls.

Fourth, do not turn Cloud Run functions into a full application host. Multiple routes, long processing, custom packages, GPU needs, and detailed container control belong in Cloud Run, Cloud Run jobs, or Workflows.

Fifth, include cost and cleanup in the prompt. Cloud Run may scale to zero, but Artifact Registry, Cloud Build, Cloud Storage, Eventarc, and Cloud Logging can still create costs.


Review Prompt

Review this Cloud Run functions implementation.
Check:
- Functions Framework registration, gcloud run deploy --function, and package.json target match
- HTTP authentication, input validation, and error responses are safe
- Eventarc retries cannot create duplicate side effects
- Secret Manager values are not logged or returned in exceptions
- The runtime service account has only the minimum IAM roles
- Cloud Logging entries are structured enough for incident review
- Any workload that should be Cloud Run is not forced into a function

If there are issues, return severity, reason, corrected code, and verification commands.

ClaudeCodeLab turns these operational checklists into products and templates and team training. They are useful when you want serverless reviews to be repeatable instead of depending on one senior engineer’s memory.


Official Docs


Result

The tested shape is small but production-like: Node.js 24 Cloud Run functions, local Functions Framework commands, curl-based HTTP and CloudEvent checks, Secret Manager injection, Firestore-backed idempotency, and Cloud Logging queries. The important habit is to review identity, logs, retries, region, and cost before the function receives real traffic.

#claude-code #gcp #cloud-functions #typescript #serverless #pubsub
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.