Data Analyst Agent on Google Cloud Run
The Data Analyst Agent lets a user upload a small data file (CSV, TSV, JSON, plain text, or Excel) and ask questions about it in plain language. Unlike a typical chat assistant, it never guesses: it writes real Python (pandas/numpy/matplotlib) and runs it — isolated inside Cloud Run's sandbox launcher (gVisor) — to compute the actual answer, and can return a genuine chart alongside its explanation. This module deploys the agent on Cloud Run v2 on top of the App_CloudRun foundation, which provisions and manages the shared Google Cloud infrastructure.
This guide focuses on the cloud services the agent uses and how to explore and operate them from the Google Cloud Console and the command line. For the mechanics common to every Cloud Run application — service identity, ingress and load balancing, scaling and concurrency, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC Service Controls, backups, and the deployment lifecycle — refer to the App_CloudRun foundation guide rather than repeating them here.
What DataAnalyst costs on RAD, and how that compares
DataAnalyst on RAD's Cloud Run module costs about US$17.58 a month in a Google Cloud project you own, plus a one-time 75-credit module fee (US$7.50). DataAnalyst is an ADK-based agent that writes and runs real Python against an uploaded data file, sandboxed inside Cloud Run.
If DataAnalyst is only needed occasionally — a demo, a trial, a seasonal or study workload — a RAD-managed project can be deleted and restored within 30 days for a handful of credits, so that US$17.58 becomes a few dollars a month instead. See Pause it for free, below.
What you pay on RAD
| In a Google Cloud project you own | In a project RAD manages for you | |
|---|---|---|
| Module fee, once per deployment | 75 credits (US$7.50 at the top-up price) | 67.5 credits (10% lower) |
| Build time | A few credits per build (roughly 3–6) | The same |
| Google Cloud running cost | Billed by Google to your own billing account, about US$17.58 a month for the default configuration (table below) | Metered hourly in credits; RAD publishes 3 credits a day, about 90 a month (about US$9 at the top-up price) |
| Who owns what | You own the project and its billing; RAD deploys and updates it | RAD owns the project, with guardrails, quotas and budget alerts; you get console access, and a minimum purchased balance is held in reserve |
10 credits cost US$1 on a one-off top-up, less on a plan. In a project RAD manages, the database, file server and network are shared by every application in the project, so a second application does not add a second database.
Default running cost in your own project (us-central1, Google list prices):
| Resource (module default) | Per month |
|---|---|
| Cloud Run, 1x vCPU / 1 GiB (scaleToZero) | US$11.58 |
| Cloud NAT and networking | US$5 |
| Cloud Storage (add-ons, backups) | US$1 |
| Total | about US$17.58 |
How it compares
- There is no comparable managed hosting offer to point to here; the honest comparison is what is managed for you — backups, the sandboxed execution environment, Secret Manager and monitoring — rather than a sticker price, since the module runs serverless and scales to zero between requests.
- RAD never claims to be cheaper than bare infrastructure in cash terms — it usually is not. The difference is what RAD manages for you: patching, backups, secrets and monitoring, plus the ability to pause DataAnalyst for free rather than paying for idle capacity (see below).
Pause it for free: delete a RAD-managed project, restore it when you need it
If DataAnalyst runs in a project RAD manages for you, you have a second option that goes well beyond scaling to zero: delete the whole project, and restore it within 30 days for close to nothing. This suits DataAnalyst that you only need occasionally far better than running it continuously.
- How it works. Deleting a RAD-managed project unlinks its billing first, then asks Google to delete the project. Google does not remove the project immediately: it keeps it, recoverable, for 30 days. Because billing is already unlinked, nothing is charged while it waits. Unlike deleting one module, this does not tear down Cloud Run resources one by one — the whole project, and everything in it, simply stops.
- Restoring costs a handful of credits, not a rebuild. Within 30 days, the project's owner can restore it. RAD asks Google to undelete the project and reattaches its billing account, then asks you to run Update on each deployment to confirm everything came back. Because nothing was individually destroyed, that Update finds the same resources already there — it is a check, not a rebuild, and an Update never charges the module fee again. That is a handful of credits (under US$1) in total for a typical DataAnalyst chain, against the 80 credits a full redeploy costs.
- So a month of occasional use can cost a few dollars, not US$17.58. Deploy DataAnalyst, use it, delete the project. Restore it next time you want it, confirm with Update, and delete it again when you're done. You pay only for the module fee once, the builds, and whatever hours it was actually live.
- What this needs. You must own the project (not a bring-your-own one RAD only manages billing for), and you restore it yourself within the 30 days — after that, Google deletes it for good. Restoring is admitted like creating a new project: your purchased credit balance must still clear the tier's floor (100 credits for the sandbox tier most study and demo use fits). Google says most services are fully working again within 36 hours of a restore.
- One real gap: the backup bucket does not survive. Nightly backups are written to a bucket inside the project, and that bucket is not protected by Cloud Storage's soft-delete, so it is very likely gone as soon as you delete the project — even though the project itself is recoverable for 30 days. If you have customised DataAnalyst and want to keep that work, copy a backup out (to Google Drive, or a bucket outside the project) before you delete. For a default installation with nothing irreplaceable in it, this does not matter.
Pay only while you use it, the other way: delete and redeploy
Cloud Run already scales to zero between requests. The option above only applies to a RAD-managed project; in your own project, or once the 30-day window has passed, the way to stop paying is to delete the deployment and deploy it again when you need it.
- What a redeploy costs. The module fee again plus the builds: about 80 credits (US$8) in your own project. Deleting saves money only once DataAnalyst would otherwise sit unused for about 14 days or more in your own project (US$0.59 a day), or about 25 days or more in a RAD-managed one (3 credits a day).
- Keep your data first. Nightly backups are written to a bucket inside the deployment and are deleted with it, so copy the latest backup out before deleting if you want to keep it.
- Note your settings. RAD does not recreate a deleted deployment for you; you enter the settings again when you deploy.
Lab sessions and Managed Environments
- Lab sessions, for training. A trainer runs a session for a class. Each participant gets the app in their own Google Cloud project for 15 minutes to 24 hours, within an allowance the trainer sets. Either the trainer funds every place, or each participant pays for their own. Everything is deleted when the session ends and unused credits go back to the trainer.
- Managed Environments, for consultancies. A partner runs the app for a client from a ring-fenced wallet it funds, and settles with the client directly. If the wallet runs low, billing pauses and the data is kept, so nobody receives an unexpected charge. At the end the partner hands the project over and the deployments become the client's own.
Credits can be bought in more than 20 currencies, including XAF, XOF, NGN, GHS, KES and ZAR, by card, bank transfer or mobile money.
Sources (8 October 2026): Google Cloud list prices from the Cloud Billing Catalog API; RAD's own fees and daily-credit estimates from radmodules.dev/pricing; Delete and restore projects for the 30-day recovery window and what it says about Cloud Storage objects without soft delete. Prices change; check each source before relying on a figure.
1. Overview
| Capability | Google Cloud service | Notes |
|---|---|---|
| Compute | Cloud Run v2 | Python (FastAPI + Google ADK) service; request-based autoscaling by default |
| AI model | Vertex AI (Gemini) | No API key secret to manage — uses the Cloud Run service's own runtime service account, granted roles/aiplatform.user |
| Code execution | Cloud Run's sandbox launcher | gVisor-isolated: deny-by-default network egress, writes discarded after each call, no access to this service's own environment variables |
| Uploaded data | Ephemeral container storage | No database and no GCS bucket at all — a file lives only under /tmp/uploads/<session>/ for the life of a chat session |
| Networking | VPC / Direct VPC Egress | Deployable standalone (inline VPC) or converged onto a shared Services_GCP network when one exists in the project |
| Ingress | Cloud Run URL | Default run.app URL; Identity-Aware Proxy strongly recommended (see below) |
Sensible defaults worth knowing up front:
- No backend credential. The agent has no API key and no database password — Vertex AI authentication is entirely the Cloud Run service's own runtime identity. The one Secret Manager secret it can hold is the visitor access key described below.
public_accessdefaults totrue. Unlike most catalogue modules, this one has no proprietary reference material to protect (it started life as a reference-catalogue chat advisor and was later re-purposed into a general-purpose sandboxed code-execution agent — see DataAnalyst_Common for that history), so there is no confidentiality reason to restrict who can self-serve deploy it.- The sandbox launcher IS the security boundary, not a convenience. Every
code-execution/file-preview tool call runs through it. Disabling
enable_sandbox_launcherremoves process isolation entirely — it is not a performance knob. - Every
execute_coderun has its own memory/CPU ceiling (execute_code_memory_limit_mb, default 512 MiB), independent of and below the container's overallmemory_limit— a runaway allocation fails cleanly withMemoryErrorrather than threatening the whole instance. - Charts are delivered by the server, not trusted to the model. The agent can generate a matplotlib chart; the image is extracted directly from the tool-call response and pushed to the browser as its own message — never relied on to survive intact inside the model's own natural-language reply.
- No hard isolation between concurrent chat sessions on one warm instance. Session directories are named with an unguessable UUID, but that is a soft boundary, not an OS-level one — see §3 and §6.
min_instance_countdefaults to0(scale-to-zero); cold start is fast since there is no repository to clone and no database connection to establish.
2. Google Cloud Services & How to Explore Them
All commands assume PROJECT and REGION are set. Service and resource names are reported
in the deployment Outputs.
A. Cloud Run — the agent service
The agent runs as a Cloud Run v2 service. Each deployment creates an immutable revision.
- Console: Cloud Run → select the service for revisions, traffic, logs, and metrics.
- CLI:
gcloud run services list --project "$PROJECT" --region "$REGION"
gcloud run services describe <service-name> --project "$PROJECT" --region "$REGION"
gcloud run revisions list --service <service-name> --project "$PROJECT" --region "$REGION"
See App_CloudRun for scaling, concurrency, execution environment, and
traffic splitting. Note max_concurrent_requests is applied via a gcloud beta run services update escape hatch after the service is created (App_CloudRun has no first-class
concurrency override) — confirm it took effect:
gcloud run services describe <service-name> --region "$REGION" \
--format='value(spec.template.spec.containerConcurrency)'
B. Vertex AI — the model backend
The agent calls Gemini via Vertex AI using the Cloud Run service's own runtime service account — there is no API key to create, store, or rotate.
- Console: Vertex AI → Model Garden, or check IAM for the
roles/aiplatform.usergrant on the runtime service account. - CLI:
gcloud projects get-iam-policy "$PROJECT" \
--flatten="bindings[].members" \
--filter="bindings.role:roles/aiplatform.user"
C. Cloud Run's sandbox launcher — code execution isolation
Not a resource you browse in the Console, but the central mechanism this module exists to
use. Applied post-deploy via a gcloud beta escape hatch (--sandbox-launcher, no
first-class Terraform field exists for it yet — see enable_sandbox_launcher).
- Confirm it's applied:
gcloud run services describe <service-name> --region "$REGION" \
--format='value(metadata.annotations)' - Confirm it's actually isolating real calls — look for
[start]/[end] exit_code=...lines in Cloud Logging around a real chat interaction:gcloud logging read 'resource.type="cloud_run_revision"
resource.labels.service_name="<service-name>" textPayload:"sandbox"' \
--project "$PROJECT" --freshness=10m --order=asc
Cloud Run sandboxes are a Public Preview feature — the exact invocation contract and availability are subject to change without GA-level stability guarantees.
D. Networking & ingress
The service is reachable at its run.app URL by default. If the project already has a
Services_GCP deployment, this module converges onto its shared VPC instead of maintaining
its own inline network — see §6 for what that means during an update.
- Console: Cloud Run (service URL); VPC network → VPC networks / Subnets.
- CLI:
gcloud run services describe <service-name> --region "$REGION" --format='value(status.url)'
gcloud compute networks list --project "$PROJECT"
See App_CloudRun for ingress settings, VPC egress, and load balancing.
E. Cloud Logging & Monitoring — structured tool-call logs
Every list_uploaded_files/inspect_file/execute_code call emits one JSON line (tool
name, session ID, success/failure, duration, exit code) — Cloud Logging parses this
automatically into jsonPayload fields and severity, with no logging library or added
dependency involved.
- Console: Logging → Logs Explorer.
- CLI — query by field, not raw text:
# every failed execute_code call in the last hour
gcloud logging read 'resource.type="cloud_run_revision"
resource.labels.service_name="<service-name>"
jsonPayload.tool="execute_code" jsonPayload.ok=false' \
--project "$PROJECT" --freshness=1h
# a specific session's activity
gcloud logging read 'resource.type="cloud_run_revision"
resource.labels.service_name="<service-name>"
jsonPayload.session_id="<session-id>"' \
--project "$PROJECT" --freshness=1h --order=asc
3. Application Behaviour
- Upload-then-ask workflow, multiple files at once. The chat page's upload control
accepts several files in one selection (uploaded sequentially, one
POST /uploadper file), so cross-file analysis (e.g. joining two CSVs) needs no separate "attach" step in the chat protocol. Files are validated by extension (allowed_upload_extensions) and a streamed size cap (max_upload_size_mb) before a single byte touches disk. - Session-scoped, ephemeral storage. Uploaded files live under
/tmp/uploads/<session_id>/for the life of a chat session — swept opportunistically the next time anyone uploads to the same instance, once older thanupload_ttl_seconds. A cold start or a different instance starts with nothing uploaded. - The agent computes; it does not guess. Every non-trivial question is answered by
writing and running real code (
execute_code), never by reasoning from a file preview alone. If the code errors, the agent reads the traceback, fixes it, and retries — visible directly in the structured logs (§2E) as a failed call followed by a successful one. - Chart generation, delivered reliably. The agent can render a matplotlib chart (Agg
backend, no display needed) by printing a
CHART_PNG_BASE64:<base64>marker line from inside the sandbox; the server extracts it from the raw tool-call response and pushes it to the browser as its own image message — independent of what the model's own text reply says, since an LLM is not reliably good at re-quoting a multi-hundred-KB blob verbatim. - Excel support.
.xlsxfiles are accepted alongside CSV/TSV/JSON/plain text (openpyxlbacks pandas' Excel engine). - Structure-aware preview for JSON. Rather than a raw byte truncation that could cut a JSON file mid-structure, the agent's file preview returns the first few list items or dict keys, pretty-printed, falling back to a raw preview only if parsing fails.
- Per-call resource ceiling. Each
execute_coderun applies its own memory (RLIMIT_AS) and CPU-time (RLIMIT_CPU) limit before the generated code executes, bounded byexecute_code_memory_limit_mband the call's own timeout — independent of, and below, the container's overallmemory_limit. - Abuse/cost guards. A per-connection message-length cap and rate limit, plus a
per-instance cap on messages per minute across every connection (
INSTANCE_MAX_MESSAGES_PER_MINUTE, default 120,0to disable), bound how much Vertex AI billing (and how manyexecute_coderuns) one caller can drive. The per-connection limit alone is defeated by opening another connection; the per-instance one is not. - An access key on a RAD-managed project. With
access_control = "auto"(default), a RAD-managed project (billed to RAD) requires a per-deployment access key whenever the service is publicly reachable (noenable_iap, ingress notinternal); a project you own is left unchanged. Visitors see a sign-in box; after the key is entered once the server sets a signed,HttpOnly,Secure,SameSite=Strictsession cookie (12 hours). Until then/uploadand/ws/chatanswer 401, and a misconfigured service with no usable key answers 503 — it fails closed. Leaveaccess_keyempty and a 32-character key is generated and shown as theaccess_keyoutput (owner and administrators only); set your own (16+ characters) to keep it off the Outputs tab. Changing it rolls a new revision and signs everyone out. The key is shared, not per-user. A deployer cannot turn it off on a RAD-managed project; only an administrator can. Scripts send it as anX-Access-Keyheader, orPOST /authwith{"key": "..."};/docs,/redocand/openapi.jsonare not served. - An open service on a RAD-managed project is scale-bounded. IAP cannot be the default (it
needs an OAuth consent screen in the project, which a freshly created RAD-managed project does
not have and can no longer be given by API), so while
enable_iap = falseandingress_settings = "all", a RAD-managed project (billed to RAD) is held tomax_instance_count <= 2andmax_concurrent_requests <= 10— the module defaults — and the plan refuses larger values. Behind IAP, withingress_settings = "internal", or in your own project, there is no such limit. - Not available in a RAD-managed sandbox or lab project. Vertex AI is not permitted in those tiers, so the plan refuses the deployment up front with a message naming the way out (a development or production RAD-managed project, or your own GCP project).
4. Configuration Variables
Variables are grouped exactly as they appear on the deployment platform. Only settings
specific to or notable for DataAnalyst_CloudRun are listed; every other input is inherited
from App_CloudRun with its standard behaviour.
Group 1 — Project & Identity
| Variable | Default | Description |
|---|---|---|
project_id | (required) | Target Google Cloud project. |
region | us-central1 | Region for the service and regional resources. |
Group 2 — Deployment Environment
| Variable | Default | Description |
|---|---|---|
tenant_id | demo | Short suffix that makes resource names unique per environment. |
support_users | [] | Emails granted project access and monitoring alerts. |
resource_labels | {} | Labels applied to all resources. |
Group 3 — Application Identity
| Variable | Default | Description |
|---|---|---|
application_name | dataanalyst | Base name for the Cloud Run service and Artifact Registry repository. |
application_display_name | Data Analyst Agent | Friendly name shown in the Console. |
application_description | (set) | Service description. |
application_version | 1.1.0 | Container image version tag — bump on every code-only redeploy, since the image is referenced by a mutable tag, not a digest. |
Group 4 — Runtime & Scaling
| Variable | Default | Description |
|---|---|---|
deploy_application | true | Set false to provision infrastructure only. |
cpu_limit | 1000m | CPU per instance. |
memory_limit | 1Gi | Memory per instance — headroom above the platform's usual 512Mi floor, since pandas/numpy loading an uploaded file benefits from it. |
min_instance_count | 0 | Minimum instances (0 = scale-to-zero). |
max_instance_count | 2 | Maximum instances. |
execution_environment | gen2 | Required by the sandbox launcher. |
container_protocol | http1 | Plain HTTP/1.1 + WebSocket. |
timeout_seconds | 600 | Kept generous since the chat WebSocket holds this open across a turn. |
Group 5 — Access & Ingress Control
| Variable | Default | Description |
|---|---|---|
ingress_settings | all | Which networks may reach the service. |
vpc_egress_setting | PRIVATE_RANGES_ONLY | How outbound traffic is routed through the VPC. |
enable_iap | false | Every message here is a real Vertex AI call AND can trigger code execution. Not the default because IAP needs an OAuth consent screen in the project; while it is off on a RAD-managed project, an access key is required and scale is bounded (see above). |
access_control | auto | auto: an access key is required on a RAD-managed project while the service is publicly reachable, unchanged on your own project; required: always; off: never (refused on a RAD-managed project). |
access_key | "" | Your own key (16+ characters). Empty generates one, shown as the access_key output. Change it to rotate. |
iap_authorized_users / iap_authorized_groups | [] | Who may access through IAP. |
Group 6 — Environment Variables & Secrets
| Variable | Default | Description |
|---|---|---|
environment_variables | {} | Extra non-secret settings. |
secret_environment_variables | {} | Not used by this module by default — the agent has no secret of its own. |
Group 8 — CI/CD & Binary Authorization
Standard App_CloudRun Cloud Build / Cloud Deploy integration — see App_CloudRun. Key input specific to this module:
| Variable | Default | Description |
|---|---|---|
additional_cloudrun_sa_roles | ["roles/aiplatform.user"] | Extra IAM roles for the Cloud Run service account. Must keep roles/aiplatform.user — removing it breaks every chat message with a Vertex AI permission error. |
Group 10 — Domain, CDN & Cloud Armor
Standard App_CloudRun load-balancer options (application_domains, enable_cdn,
enable_cloud_armor, admin_ip_ranges) — see App_CloudRun.
Group 14 — Observability & Health
| Variable | Default | Description |
|---|---|---|
startup_probe / liveness_probe | HTTP GET /_health | Matches the FastAPI server's health route. Not named /healthz — confirmed live that Cloud Run's edge routing treats that exact path as reserved and never reaches the container. |
uptime_check_config | disabled | Cloud Monitoring uptime check. |
Group 15 — Data Analyst Agent Configuration
| Variable | Default | Description |
|---|---|---|
vertex_region | us-central1 | Vertex AI region serving the agent's Gemini calls. |
agent_model | gemini-2.5-flash | Gemini model ID used via Vertex AI. |
enable_sandbox_launcher | true | The actual security boundary for code execution — see §1. |
max_upload_size_mb | 20 | Enforced by streaming the upload and rejecting once this many bytes have arrived — never trusted from the request's declared size. |
allowed_upload_extensions | [".csv", ".tsv", ".json", ".txt", ".xlsx"] | Extensions the upload endpoint accepts. |
upload_ttl_seconds | 3600 | How long an uploaded file is kept before cleanup eligibility. |
execute_code_memory_limit_mb | 512 | Per-execute_code-call memory ceiling — keep comfortably below memory_limit. |
max_concurrent_requests | 10 | Cap on concurrent WebSocket connections per instance — also the knob to set to 1 if the cross-session isolation caveat (§6) matters for your data. |
require_services_gcp_module | false | This module has no database/NFS/GKE dependency of its own, so it deploys standalone into a blank project by default. |
Group 22 — VPC Service Controls & Audit Logging
Standard App_CloudRun options (enable_vpc_sc, vpc_cidr_ranges, vpc_sc_dry_run,
organization_id, enable_audit_logging) — see App_CloudRun.
5. Outputs
| Output | Description |
|---|---|
service_name | Cloud Run service name. |
service_url | Default run.app URL — open this to chat with the agent. |
access_key_required | Whether the agent demands an access key. |
access_key | The generated access key to enter on the sign-in screen (empty when you chose your own key or none is required). |
service_location | Region the service runs in. |
sandbox_launcher_enabled | Whether --sandbox-launcher was applied. |
container_image / container_registry | Deployed image and Artifact Registry repo. |
network_name / network_exists | VPC network in use, and whether it was discovered (shared) rather than created inline. |
storage_buckets | Always empty — this module provisions no GCS bucket. |
monitoring_enabled | Monitoring status. |
deployment_id / tenant_id / resource_prefix | Naming identifiers. |
project_id / project_number | Project identifiers. |
6. Configuration Pitfalls & Sensible Defaults
Risk: Critical (data loss / outage / security) — High (service degraded) — Medium (cost or partial degradation) — Low (minor).
| Setting | Sensible value | Risk | Consequence if wrong |
|---|---|---|---|
enable_sandbox_launcher | true | Critical | false removes the ONLY isolation between LLM-generated code and the rest of the container — the module's entire security model depends on this staying on. |
enable_iap / access_control | IAP, or an access key, for anything beyond a quick test | High | On your own project with access_control = "auto" and IAP off, the service is open: any caller can drive Vertex AI billing and code-execution runs, bounded only by the rate limits. Set access_control = "required" or enable IAP. |
execute_code_memory_limit_mb | comfortably below memory_limit | High | Set too close to (or above) memory_limit, a single pathological allocation can still threaten the whole instance instead of failing cleanly with MemoryError. |
max_concurrent_requests | 1 for genuinely sensitive data | Medium | Session directories are isolated only by an unguessable name, not an OS-level permission boundary — concurrent sessions on one warm instance can, in principle, read each other's uploaded files. |
application_version | bump on every code-only change | Medium | The image is referenced by a mutable tag; rebuilding under the same tag creates no new revision, so a "redeploy" silently keeps serving the old container. |
allowed_upload_extensions | keep to data formats only | Medium | Widening this to arbitrary file types expands what a sandboxed-but-real Python process can be asked to parse. |
upload_ttl_seconds | shorter for public-facing deployments | Low | A long TTL keeps a stranger's uploaded data on the instance's disk longer than necessary if the browser tab is simply abandoned. |
For the foundation behaviour referenced throughout — service identity, scaling and concurrency, ingress and load balancing, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC-SC, backups, and image mirroring — see App_CloudRun. The shared application configuration (container build, upload handling, and the sandboxed code-execution model) is described in DataAnalyst_Common.
Related guides
- DataAnalyst Common — Shared Application Configuration — the configuration shared by this deployment target.
Need RAD to do something it does not do yet? Request it on the roadmap, or vote on what is already there.