HomeAbout MeBook a Call

Automating GCP Cloud Run with Google Docs A Workspace as a Service Pattern

By Vo Tu Duc
May 06, 2026
Automating GCP Cloud Run with Google Docs A Workspace as a Service Pattern

Self-service tooling is meant to empower teams, but a critical disconnect between user intent and platform complexity creates an operational bottleneck that stifles innovation.

image 0

The Challenge: The Operational Bottleneck in Self-Service Tooling

In a perfect world, every team—from data science and analytics to marketing and research—would have immediate, on-demand access to the cloud resources they need. The reality is often a landscape of friction, delays, and workarounds. The promise of “self-service” frequently collides with the complexity of the underlying platforms, creating a significant operational bottleneck that stifles innovation and agility. The core issue isn’t a lack of powerful tools; it’s the impedance mismatch between the user’s intent and the mechanism for fulfilling it.

Why traditional infrastructure provisioning slows down non-technical teams

For a data analyst, the goal is simple: “I need a sandboxed environment with my standard libraries to run this data processing script against our new dataset.” For an operations engineer, the translation of that request is a cascade of technical considerations: “I need to define a Cloud Run service, select the right container image, configure a service account with scoped IAM permissions to a specific BigQuery table, set appropriate CPU/memory limits, and configure VPC connector settings for private network access.”

This gap leads to several failure modes:

  • The Ticket Queue: The most common pattern is a request via a system like Jira or ServiceNow. This initiates a human-powered workflow filled with potential delays. The operations team becomes a bottleneck, context-switching between provisioning requests and their core engineering work. Back-and-forth clarification on requirements is common, stretching a five-minute task into a multi-day ordeal.

  • The Overly-Complex UI: Exposing a tool like the GCP Console, Terraform Cloud, or even a custom-built Internal Developer Platform (IDP) directly to non-technical users is often counter-productive. These interfaces are built for engineers. They present a dizzying array of options and assume a deep understanding of concepts like IAM roles, container registries, networking rules, and billing SKUs. The cognitive overhead is immense, leading to user frustration, misconfigurations, or complete avoidance of the tool.

image 1
  • The Rise of Shadow IT: When official channels are too slow or complex, motivated teams find their own solutions. This often means running critical workloads on local machines, using unsanctioned SaaS products, or leveraging personal cloud accounts. While this solves the immediate problem for the user, it creates a nightmare for the organization in terms of security, compliance, and cost management.

The fundamental problem is that we ask non-technical users to speak the language of infrastructure. We force them to understand the how (the implementation details) when all they care about is the what (the outcome).

Introducing the ‘Workspace-as-a-Service’ (WaaS) concept

To break this cycle, we need to shift our mindset from provisioning infrastructure to providing workspaces. This is the core idea behind a “Workspace-as-a-Service” (WaaS) model.

A WaaS is a user-centric abstraction layer that delivers a pre-configured, isolated, and purpose-built environment on demand. The user no longer requests individual components like a “Cloud Run service” or a “service account.” Instead, they request a “Data Analysis Workspace” or a “Report Generation Environment.”

Think of it like checking into a hotel. You don’t submit a request for concrete, steel beams, plumbing fixtures, and electrical wiring. You request a “king suite with a city view.” The hotel’s operational backend handles the complex orchestration to deliver a complete, secure, and functional room that meets your specifications.

A true WaaS platform embodies several key principles:

  • Declarative and Intent-Based: The user declares their desired outcome, not the steps to achieve it.

  • Templated and Governed: The platform team defines a catalog of pre-approved workspace “templates.” These templates have security, networking, and cost controls baked in, enforcing organizational best practices by default.

  • Ephemeral and Isolated: Workspaces are designed to be temporary. They can be spun up for a specific task and torn down cleanly afterward, preventing resource sprawl and ensuring a consistent starting state for every job.

  • Frictionless User Experience: The interface for requesting a workspace should be incredibly simple, ideally using tools the user already knows and trusts.

Blueprint goals: A secure and auditable [Automated Job Creation in Real Time Jobber and Google Sheets Integration from Gmail](https://votuduc.com/Automated-Job-Creation-in-Jobber-from-Gmail-p115606) pattern

Building a WaaS model requires a robust Automated Quote Generation and Delivery System for Jobber pattern that is not only user-friendly but also satisfies the stringent requirements of security and operations teams. As we design our solution, we will anchor it to three primary goals:

  1. Security by Design: Security cannot be an afterthought. Our pattern must enforce a zero-trust model where every workspace is provisioned with the principle of least privilege. This means automatically creating narrowly-scoped identities, enforcing network isolation, and providing a secure mechanism for managing secrets. The goal is to build guardrails that make the secure path the easiest path, not gates that simply block users.

  2. Auditability and Traceability: Every action must be logged. We need an immutable, chronological record of who requested what workspace, what parameters they used, and when it was created and destroyed. This audit trail is non-negotiable for compliance, security forensics, and cost allocation. The request mechanism itself should serve as a durable, version-controlled source of truth.

  3. Simplicity and Accessibility: The entire system fails if the intended users can’t or won’t use it. The request interface must be radically simple, eliminating the need for any specialized training, CLI tools, or complex web forms. The workflow should be asynchronous and event-driven, notifying the user when their workspace is ready without forcing them to watch a progress bar.

Achieving these goals with a single, elegant solution is the challenge we’re tackling. In the following sections, we will demonstrate how to build this exact pattern by combining the familiarity of Google Docs with the power of GCP’s serverless and Architecting an Event-Driven Workspace with PubSub Firebase and Gemini.

High-Level Architectural Overview

Before we dive into the code and configuration, let’s zoom out and understand the architecture at a 10,000-foot view. This pattern elegantly connects a user-friendly frontend (Google Docs) with a powerful, serverless backend ([Architecting The Workspace as a Service Pattern with Google Workspace and Cloud Run with GCP Cloud Run](https://votuduc.com/architecting-workspace-as-a-service-with-gcp-cloud-run-p-20260322385124)), using [AI Powered Cover Letter Automated Work Order Processing for UPS Engine](https://votuduc.com/AI-Powered-Cover-Letter-Automation-Engine-p111092) as the essential middleware. The goal is to create a seamless, self-service workflow that abstracts away the underlying complexity of cloud infrastructure.

At its core, this system transforms a simple document into a dynamic control plane. A user defines the desired state of a service in a familiar table format, clicks a button, and the machinery in the background springs to life to make that state a reality.

System flow diagram from user request to resource creation

To visualize how a request travels through the system, let’s trace the journey from a user filling out a form to a new Cloud Run service being live and accessible.

  1. User Request (The Manifest): A user opens a designated Google Doc template. They fill in a table with the specifications for their desired Cloud Run service—things like the service name, the container image URL, required environment variables, and memory allocation. This document acts as a human-readable “manifest.”

  2. Trigger (The “Go” Button): The user clicks a custom menu item within the Google Doc, labeled something like “Deploy Service.” This action is the trigger that initiates the entire automated workflow.

  3. Orchestration (Apps Script Awakens): The menu click invokes a function in the Genesis Engine AI Powered Content to Video Production Pipeline bound to the document. This script acts as our central orchestrator. It reads the data from the table, validates it, and constructs a JSON payload.

  4. Secure API Call (The Hand-off): The Apps Script uses the UrlFetchApp service to make an authenticated HTTPS POST request to a predefined endpoint. This endpoint is a dedicated “provisioner” service running on GCP Cloud Run. The request is authenticated using a Google-signed ID token, ensuring that only our Apps Script can invoke this endpoint.

  5. Execution (The Provisioner Takes Over): The provisioner Cloud Run service receives the request. This service is a small, containerized application (e.g., written in JSON-to-Video Automated Rendering Engine or Go) whose sole purpose is to interact with GCP APIs.

  6. GCP API Interaction (The Heavy Lifting): Using the GCP SDKs and the permissions granted to its attached IAM Service Account, the provisioner service parses the incoming JSON payload. It then programmatically makes calls to the Cloud Run Admin API to create or update a service matching the user’s specifications.

  7. Resource Creation (It’s Alive!): The Cloud Run Admin API processes the request, pulls the specified container image, and deploys it as a new, fully managed Cloud Run service.

  8. Feedback Loop (Closing the Loop): Once the deployment is successful (or if it fails), the provisioner service sends a response back to the Apps Script. This response typically includes the URL of the newly created service or a detailed error message.

  9. UI Update (Confirmation): The Apps Script receives this response and updates the original Google Doc. It might write the new service URL and a “Deployed” status back into the table, providing direct feedback to the user right where they started.

Component 1: Google Docs as the Human-Friendly UI

The choice of Google Docs as the user interface is deliberate and powerful. It’s not just a text editor; it’s a collaborative, version-controlled, and universally understood control surface.

  • Zero Learning Curve: Nearly everyone in a modern organization knows how to use Google Docs. There’s no need to train users on a new portal, understand complex forms, or learn a new tool. They simply fill out a table.

  • Built-in Audit Trail: Google Docs’ version history provides a natural, immutable log of who requested what and when. This is invaluable for tracking changes and understanding the history of a service request without building a separate auditing system.

  • Collaborative Workflows: A request can be drafted, reviewed, and commented on by multiple team members directly within the document. You can implement simple approval workflows just by having a manager leave a comment or change a “Status” field from “Pending” to “Approved.”

  • Structured Simplicity: By using tables, we enforce a structured data entry format. This ensures that the downstream automation receives the exact parameters it needs, minimizing user error while abstracting away the complexity of YAML or command-line flags.

Component 2: [Architecting Multi Tenant AI Workflows in Building Modular Agentic Apps Script with Gemini Function Calling](https://votuduc.com/architecting-multi-tenant-ai-workflows-in-google-apps-script-p-20260321290501) as the Central Orchestrator

If Google Docs is the face of our system, Google Apps Script is the central nervous system. It’s the “glue code” that connects the user’s intent in the document to the execution engine in GCP.

  • Native Workspace Integration: Apps Script lives inside [Automatically create new folders in Google Drive, generate templates in new folders, fill out text automatically in new files, and save info in [Automated Web Scraping with [Multilingual Text-to-Speech Tool with SocialSheet Streamline Your Social Media Posting 123](https://votuduc.com/Multilingual-Text-to-Speech-Tool-with-Google-Workspace-p809282)](https://votuduc.com/Automated-Web-Scraping-with-Google-Sheets-p292968)](https://workspace.google.com/marketplace/app/auto_create_folder_and_files/430076014869) and has first-class access to the Document Object Model (DOM) of a Google Doc. This makes it trivial to read data from a table (table.getCell(row, col).getText()) or write results back.

  • Event-Driven Execution: It’s designed to be triggered by events—a menu click, a document opening, or a time-based trigger. This allows us to build responsive, interactive experiences directly within the document.

  • Secure Outbound Communication: Using the UrlFetchApp service, Apps Script can make authenticated HTTP requests to external services. It can generate and attach Google-signed ID tokens to these requests, allowing our Cloud Run provisioner to securely verify that the call is legitimate and is coming from our specific script. This is a critical security feature that avoids exposing service account keys or other static credentials.

Component 3: GCP Cloud Run as the Serverless Execution Engine

The final piece of the puzzle is the provisioner service running on Cloud Run. This is where the actual interaction with Google Cloud’s infrastructure occurs. We intentionally delegate this “heavy lifting” from Apps Script to Cloud Run for several key reasons.

  • Principle of Least Privilege: The provisioner service runs with a dedicated IAM Service Account. We can grant this service account the precise permissions it needs (e.g., roles/run.admin to manage Cloud Run services, roles/iam.serviceAccountUser to act as the service) and nothing more. This is far more secure and maintainable than trying to manage GCP credentials within the Apps Script environment.

  • Power and Flexibility: We can write the provisioner logic in any language that can be containerized (Python, Go, Node.js, etc.), allowing us to use robust, well-supported GCP Server-Side Libraries. This makes complex tasks like configuring VPC connectors, setting up secrets, or managing traffic splitting much more straightforward than they would be in Apps Script.

  • Scalability and Cost-Effectiveness: As a serverless platform, Cloud Run scales down to zero. This means our provisioner service costs absolutely nothing when it’s not actively handling a deployment request. It’s the perfect model for an on-demand, event-driven task like this one. It can also handle concurrent deployment requests without any additional configuration.

Step 1: Designing the Request Interface in Google Docs

Before we can automate anything, we need a source of truth—a place where a request is defined. Instead of building a custom web front-end, we’re co-opting a tool that’s ubiquitous, collaborative, and surprisingly scriptable: Google Docs. This document will serve as our “request form,” a human-readable interface for defining a Cloud Run deployment job. The beauty of this approach lies in its simplicity and accessibility; virtually anyone can fill out a document.

Our goal here is to create a structured document that is easy for a human to understand and, crucially, for our script to parse reliably.

Structuring the Google Doc as a request form

The key to making a Google Doc machine-readable is structure. Free-form text is a nightmare to parse, but a simple table provides the perfect key-value format we need. We’ll design our document template with a primary table that holds all the necessary parameters for a Cloud Run deployment.

Create a new Google Doc to serve as your template. Inside, create a two-column table. The left column will hold the parameter name (our “key”), and the right column will hold the user-provided “value”.

Here’s a sample structure for deploying a simple Cloud Run service:

| Parameter | Value |

| --------------------- | ------------------------------------------------------------------ |

| Service Name | my-awesome-app |

| Container Image | us-central1-docker.pkg.dev/gcp-project-id/my-repo/my-image:latest |

| Region | us-central1 |

| Memory Limit | 512Mi |

| CPU | 1 |

| Environment Vars | DB_HOST=10.0.0.1,SECRET_KEY=some-value |

| Requester Email | [email protected] |

| Job Status | PENDING |

Design Considerations:

  • Clarity is King: Use clear, unambiguous parameter names. Add a brief “Instructions” section at the top of the document explaining each field, especially complex ones like Environment Vars.

  • Data Formatting: For parameters like Environment Vars, establish a simple, consistent format (e.g., a comma-separated list of KEY=VALUE pairs). This makes parsing predictable.

  • Status Field: Including a Job Status field is critical. Our automation will update this field directly in the document, providing a feedback loop for the user (PENDING -> RUNNING -> SUCCESS / FAILED).

  • Template-Driven: This document should be treated as a template. Users will make a copy of it for each new deployment request, fill in the values, and then trigger the automation.

Using Apps Script to create a custom ‘Submit Job’ menu

With the structure defined, we need a trigger—a way for the user to say, “This request is ready. Go!” This is where Google Apps Script comes in. We’ll add a custom menu item directly into the Google Docs UI.

  1. Open your Google Doc template.

  2. Navigate to Extensions > Apps Script. This will open a new browser tab with the Apps Script editor.

  3. Replace any boilerplate code with the following:


/**

* @OnlyCurrentDoc

*

* The onOpen function runs automatically when the Google Doc is opened.

* It's used here to add a custom menu to the Doc's UI.

*/

function onOpen() {

DocumentApp.getUi()

.createMenu('Cloud Run Actions')

.addItem('Submit Job', 'submitJob')

.addToUi();

}

/**

* A placeholder for our main function. This will be triggered

* when a user clicks the 'Submit Job' menu item.

*/

function submitJob() {

// We will build the parsing logic here in the next step.

DocumentApp.getUi().alert('Job submission initiated!');

}

  1. Save the script project (give it a name like “Cloud Run Submitter”).

Now, go back to your Google Doc and reload the page. After a moment, you should see a new menu item appear named Cloud Run Actions. Clicking it will reveal a single option: Submit Job. The first time you run it, Google will prompt you to authorize the script’s permissions. This simple onOpen() function is the hook that transforms our static document into an interactive application interface.

Parsing document content to capture request parameters

The final piece of this step is to make the Submit Job button actually do something. We need to write the logic that reads the table from our document and converts it into a structured data object (a JavaScript object, in this case) that we can use later.

We’ll implement this logic inside the submitJob function we created earlier. The strategy is to find the first table in the document, iterate through its rows, and build an object from the key-value pairs in its cells.

Update your submitJob function in the Apps Script editor with the following code:


/**

* Parses the request parameters from the first table in the document

* and prepares the data for submission.

*/

function submitJob() {

const ui = DocumentApp.getUi();

try {

const doc = DocumentApp.getActiveDocument();

const body = doc.getBody();

const tables = body.getTables();

if (tables.length === 0) {

throw new Error('No request table found in the document.');

}

const requestTable = tables[0]; // We assume the request form is the first table

const numRows = requestTable.getNumRows();

const requestParams = {};

// Iterate through table rows, skipping the header row (i=0)

for (let i = 1; i < numRows; i++) {

const row = requestTable.getRow(i);

// Ensure the row has at least two cells to avoid errors

if (row.getNumCells() < 2) continue;

const key = row.getCell(0).getText().trim();

const value = row.getCell(1).getText().trim();

// Only add the parameter if a key is present

if (key) {

requestParams[key] = value;

}

}

// For now, we'll display the parsed object in an alert for verification.

// In the next major step, we will send this object to our backend API.

const message = `Parsed Parameters:\n\n${JSON.stringify(requestParams, null, 2)}`;

ui.alert('Parsing Successful!', message, ui.ButtonSet.OK);

// TODO: Send 'requestParams' object to the backend service.

} catch (error) {

ui.alert('Error', `Failed to parse job request: ${error.message}`, ui.ButtonSet.OK);

}

}

Code Breakdown:

  1. Error Handling: We wrap the logic in a try...catch block. If we can’t find a table or something else goes wrong, we’ll show the user a clean error message instead of failing silently.

  2. Get the Table: body.getTables()[0] grabs the first table in the document. This is a key convention: our script relies on the request form being the first table.

  3. Iterate and Extract: The for loop walks through each row (skipping the header at index 0). For each row, it extracts the text from the first cell (key) and the second cell (value). The .trim() function is essential for removing accidental leading or trailing whitespace.

  4. Build the Object: It populates the requestParams JavaScript object, creating a clean, structured representation of the form data.

  5. Verification: The ui.alert() call using JSON.stringify is a powerful debugging step. It displays the final parsed object, allowing you to confirm that your script is reading the document correctly before you move on to wiring up more complex backend logic.

With this script in place, you now have a fully functional data entry and parsing system built entirely within Google Docs. You can fill out the form, click Cloud Run Actions > Submit Job, and see the resulting JSON object. We’ve successfully laid the foundation for our automation pipeline.

Step 2 Orchestrating the API Call with Apps Script

With our Cloud Run Job ready and waiting, we now turn to the engine of our automation: Google Apps Script. This is where the magic happens. Apps Script acts as the bridge, taking the trigger from our Google Doc and translating it into a secure, authenticated API call to GCP. It’s a serverless JavaScript environment embedded within AC2F Streamline Your Google Drive Workflow, making it the perfect tool for this kind of integration.

Our script will perform three critical functions: securely authenticate with Google Cloud, build the specific request to run our job, and then execute that request, handling the immediate result.

Securing the connection with OAuth2 for Google Cloud APIs

Before we can even think about talking to the Cloud Run API, we need to establish a secure, authorized channel. We can’t just send a request from an anonymous script; we need to prove that our script has been granted permission to act on our behalf. This is handled seamlessly by Apps Script using the OAuth2 protocol.

First, you must link your Apps Script project to the corresponding Google Cloud Platform (GCP) project. In the Apps Script editor, navigate to Project Settings (the gear icon ⚙️) and find the “Google Cloud Platform (GCP) Project” section. Enter the Project Number of your GCP project where the Cloud Run Job is deployed. This association is crucial for enabling the necessary APIs and managing permissions.

Next, we need to request the correct “scope” in our script. A scope is a declaration of the permissions our script needs. For interacting with most GCP services, including Cloud Run, the cloud-platform scope is required. When a user runs the script for the first time, Google will present a consent screen detailing these requested permissions.

The core of this process in Apps Script is the ScriptApp.getOAuthToken() method. This function handles the entire OAuth2 flow, returning a short-lived access token that we can include in our API request header.

Here’s how you get the token:


/**

* Retrieves an OAuth2 token for authenticating with Google Cloud APIs.

*/

function getGcpAuthToken_() {

// The script must be associated with a GCP project for this to work.

// This will trigger the OAuth consent screen on the first run.

return ScriptApp.getOAuthToken();

}

By simply calling this function, Apps Script manages the token generation and refresh complexity behind the scenes. This token is our key to the GCP kingdom, proving our script’s identity and authorization with every call.

Constructing the payload for the Cloud Run Jobs API

With authentication sorted, our next task is to craft the precise message we’ll send to the Cloud Run Jobs API. We’ll be interacting with the run method of the API, which, as the name implies, initiates a new execution of our job.

The request requires two main components: the API endpoint URL and a JSON payload.

The endpoint URL follows a standard format:

https://run.googleapis.com/v2/projects/YOUR_PROJECT_ID/locations/YOUR_REGION/jobs/YOUR_JOB_NAME:run

You must replace the placeholders YOUR_PROJECT_ID, YOUR_REGION, and YOUR_JOB_NAME with the specific values for your setup.

The payload is a JSON object where we can specify overrides for the job’s default configuration. This is incredibly powerful, as it allows us to pass dynamic data from our Google Doc directly into the job execution. For example, we can override container arguments (args) or environment variables (env).

Let’s imagine our Google Doc contains a specific customer ID and a report type we want our job to process. We can construct a payload to pass these values as container arguments.


/**

* Constructs the JSON payload for the Cloud Run Jobs API 'run' method.

* @param {string} customerId - The customer ID to process.

* @param {string} reportType - The type of report to generate.

* @return {string} A stringified JSON payload.

*/

function buildCloudRunPayload_(customerId, reportType) {

const payload = {

"overrides": {

"containerOverrides": [

{

"args": [

"--customer-id",

customerId,

"--report-type",

reportType

],

// Alternatively, you could override environment variables:

// "env": [

//   { "name": "CUSTOMER_ID", "value": customerId },

//   { "name": "REPORT_TYPE", "value": reportType }

// ]

}

]

}

};

return JSON.stringify(payload);

}

In this function, we create a JavaScript object that mirrors the required JSON structure. The overrides.containerOverrides[0].args array is where we inject our dynamic data. The job’s container must be configured to parse these command-line arguments. Finally, we use JSON.stringify() to convert the object into the string format required for the HTTP request.

Executing the API call and handling the initial response

We have our authentication token and our data payload. It’s time to put it all together and launch the job. We’ll use Apps Script’s built-in UrlFetchApp service to make the HTTP POST request.

We need to assemble an options object that includes the HTTP method, headers, and the payload. The most critical header is Authorization, where we’ll place our OAuth token, prefixed with “Bearer “.


/**

* Executes a call to the Cloud Run Jobs API to start a new execution.

* @param {string} projectId - Your GCP Project ID.

* @param {string} region - The region of your Cloud Run Job (e.g., "us-central1").

* @param {string} jobName - The name of your Cloud Run Job.

* @param {string} payload - The stringified JSON payload for the request body.

* @return {Object} The parsed JSON response from the API.

*/

function executeCloudRunJob_(projectId, region, jobName, payload) {

const apiEndpoint = `https://run.googleapis.com/v2/projects/${projectId}/locations/${region}/jobs/${jobName}:run`;

const token = getGcpAuthToken_();

const options = {

'method': 'post',

'contentType': 'application/json',

'headers': {

'Authorization': 'Bearer ' + token

},

'payload': payload,

'muteHttpExceptions': true // Prevent script from halting on API errors

};

const response = UrlFetchApp.fetch(apiEndpoint, options);

const responseCode = response.getResponseCode();

const responseBody = response.getContentText();

if (responseCode === 200) {

Logger.log("Successfully initiated Cloud Run Job execution.");

return JSON.parse(responseBody);

} else {

Logger.log(`Error calling Cloud Run API. Status: ${responseCode}, Body: ${responseBody}`);

throw new Error(`Failed to run job. Received status ${responseCode}.`);

}

}

It’s crucial to understand that the Cloud Run Jobs run API is asynchronous. When you call this endpoint, it doesn’t wait for the job to complete. Instead, it immediately returns a “Long-Running Operation” (LRO) object. This LRO is an acknowledgment that GCP has accepted your request and has created a new job execution.

The response object you get back will look something like this:


{

"name": "projects/your-project-id/locations/us-central1/operations/...",

"metadata": {

"@type": "type.googleapis.com/google.cloud.run.v2.Execution",

"name": "projects/your-project-id/locations/us-central1/executions/my-report-job-abcdef"

}

}

The most important piece of information here is metadata.name. This is the unique identifier for the specific execution we just started. We can log this ID back to our Google Doc or a related Google Sheet to create an audit trail, allowing us to track the status of this specific run later on. Our script has now successfully orchestrated the handoff, and our container is spinning up in the cloud to do its work.

Step 3: Executing the Task with a GCP Cloud Run Job

With our trigger mechanism established, we now need a robust, containerized environment to execute the actual task. This is the core of our “Workspace as a Service” pattern—a transient, on-demand compute environment that performs a specific action and then disappears. For this, Google Cloud Run Jobs is the perfect tool. Unlike Cloud Run Services, which are designed to continuously serve requests, Cloud Run Jobs are designed to run a task to completion and then terminate.

Why Cloud Run is ideal for containerized on-demand tasks

Cloud Run offers a compelling set of features that make it a superior choice for this pattern over alternatives like virtual machines or persistent server setups.

  • Serverless Execution: The primary advantage is its serverless nature. You provide a container image, and Google Cloud handles the underlying infrastructure. There are no VMs to provision, patch, or manage. The job starts, executes its logic, and the resources are automatically deallocated upon completion.

  • Pay-for-Use Model: You are billed only for the exact duration your job’s container is running, down to the nearest 100 milliseconds. For infrequent, on-demand tasks triggered from a Google Doc, this is exceptionally cost-effective. If no one requests a workspace, your cost is zero.

  • Containerization as a Standard: Cloud Run is built on containers. This allows you to package your application, its dependencies, runtime, and any necessary tools (like Terraform, gcloud CLI, kubectl, or custom binaries) into a single, immutable, and portable artifact. This guarantees that your task runs the same way every single time, regardless of when or where it’s executed.

  • Seamless GCP Integration: As a native GCP service, Cloud Run Jobs integrate effortlessly with the rest of the ecosystem. It uses IAM for permissions via service accounts, can pull container images directly from Artifact Registry, and automatically streams all stdout and stderr output to Cloud Logging without any configuration.

Building a sample container for an infrastructure task

Let’s create a practical example. Imagine our Google Doc is used to request the creation of a new, temporary Google Cloud Storage (GCS) bucket for a data analysis project. Our Cloud Run Job will be responsible for executing this request.

The task requires two key components: a Dockerfile to define the container image and a script to perform the action.

1. The Entrypoint Script (main.sh)

This simple shell script will be the heart of our job. It reads an environment variable to get the desired bucket name and then uses the gsutil command-line tool to create it.


#!/bin/bash

# Exit immediately if a command exits with a non-zero status.

set -e

# --- Configuration ---

# The BUCKET_NAME is passed as an environment variable by the Cloud Run Job.

# We provide a default value for local testing.

BUCKET_NAME="${BUCKET_NAME:-my-default-test-bucket}"

GCP_PROJECT_ID=$(gcloud config get-value project)

LOCATION="US-CENTRAL1"

# --- Logging ---

echo "{\"severity\": \"INFO\", \"message\": \"Job started.\", \"project_id\": \"${GCP_PROJECT_ID}\", \"bucket_name\": \"${BUCKET_NAME}\"}"

# --- Main Logic ---

echo "Attempting to create GCS bucket: gs://${BUCKET_NAME}"

if gsutil mb -p "${GCP_PROJECT_ID}" -l "${LOCATION}" "gs://${BUCKET_NAME}"; then

echo "{\"severity\": \"NOTICE\", \"message\": \"Successfully created GCS bucket.\", \"bucket_url\": \"gs://${BUCKET_NAME}\"}"

else

# This block will only be reached if 'set -e' is removed.

# With 'set -e', the script will exit on gsutil failure.

echo "{\"severity\": \"ERROR\", \"message\": \"Failed to create GCS bucket.\"}"

exit 1

fi

# --- Completion ---

echo "{\"severity\": \"INFO\", \"message\": \"Job finished successfully.\"}"

exit 0

2. The Dockerfile

This file defines how to build our container image. We’ll use a base image provided by Google that already includes the gcloud SDK (and therefore gsutil), making our job much easier.


# Use the official Google Cloud SDK image as a base.

# The 'slim' version is smaller and contains the essential tools.

FROM google/cloud-sdk:slim

# Set the working directory inside the container.

WORKDIR /app

# Copy the entrypoint script into the container.

COPY main.sh .

# Make the script executable.

RUN chmod +x main.sh

# Define the command to run when the container starts.

ENTRYPOINT ["./main.sh"]

With these two files, you would build the container image and push it to Google’s Artifact Registry. The Cloud Run Job would then be configured to pull and execute this specific image, passing the required BUCKET_NAME as an environment variable extracted from the Google Doc in the previous step.

Best practices for logging and status reporting within the job

A job that runs silently is difficult to debug and provides a poor user experience. Implementing robust logging and status reporting is critical.

  • Embrace Structured Logging:

Instead of printing plain text like echo "Creating bucket...", write JSON-formatted strings to standard output. Cloud Logging is optimized to parse these JSON payloads, turning them into structured log entries. This makes your logs searchable, filterable, and easy to set alerts on. Notice how the main.sh script above uses echo to print JSON objects with severity, message, and other context-rich fields. This is a simple yet powerful technique.

  • Use Exit Codes to Signal State:

The most fundamental way a job communicates its final status is through its exit code.

  • Exit Code 0: Signals success.

  • Any Non-Zero Exit Code (e.g., 1): Signals failure.

Cloud Run uses this exit code to mark the job execution as “Succeeded” or “Failed” in the console and API. Our sample script uses set -e, which automatically causes the script to exit with a non-zero status if any command fails, ensuring failures are correctly reported.

  • Report Progress Back to the Source:

For a truly integrated “Workspace as a Service” experience, the job should communicate its status back to the user in the Google Doc. While detailed implementation is beyond this section, the pattern is straightforward:

  1. Grant the Cloud Run Job’s service account the necessary IAM permissions to edit Google Docs via the API.

  2. Pass the documentId of the triggering Google Doc to the job as an environment variable.

  3. Within your execution script, use the Google Docs API (e.g., via a client library or curl) to insert text at key stages: “In Progress,” “GCS Bucket Created: gs://…”, or “Error: Bucket name already exists.” This provides a closed-loop feedback mechanism, turning the Google Doc into a living log of the request.

Step 4: Closing the Loop: Asynchronous Feedback to the User

We’ve successfully kicked off a potentially long-running process from a simple button click in a Google Doc. But this creates a new challenge: the user is now staring at a document, completely disconnected from the job they just started. Did it work? Is it still running? Did it fail? Leaving the user in the dark is a poor experience.

This step is all about closing that feedback loop. We need a robust, asynchronous mechanism to monitor the Cloud Run job from our Apps Script and report the final status—success or failure—directly back into the Google Doc that started it all. This transforms the document from a simple trigger into a dynamic log and results panel.

Strategies for monitoring long-running Cloud Run jobs

The core challenge here is the stateless and time-limited nature of the initial interaction. The Apps Script function that handles the button click can’t simply wait for a 15-minute data processing job to finish; it will time out long before that. We need a way to decouple the job invocation from the status monitoring.

This is where the elegance of Cloud Run Jobs comes into play. Unlike Cloud Run Services, which are designed to continuously serve requests, Cloud Run Jobs are designed for tasks that run to completion and then stop. This model is perfect for our use case and comes with a built-in API for execution management.

When you execute a Cloud Run Job, the API immediately returns a response containing the metadata for that specific execution, including its unique name. This name is our golden ticket. We can use it to poll the Cloud Run Admin API to query the status of that specific execution.

An execution will transition through several terminal conditions, but the ones we care most about are:

  • CONDITION_SUCCEEDED: The job completed its work successfully.

  • CONDITION_FAILED: The job terminated due to an error.

By periodically checking for one of these states, we can determine when the job is finished and what its outcome was. While more complex patterns involving Pub/Sub notifications and Firestore status tracking exist, for this “Workspace as a Service” pattern, direct polling of the Cloud Run Jobs API from Apps Script offers the simplest and most direct path to a solution.

Polling the job status from Apps Script using triggers

So, how do we perform this periodic check from Apps Script? The answer lies in time-driven triggers. Think of them as cron jobs for your Automated Client Onboarding with Google Forms and Google Drive. environment. We can programmatically create a trigger that calls a specific “status checker” function every minute, five minutes, or whatever interval makes sense for your job’s expected duration.

Here’s the complete workflow:

  1. Initiate and Store: The initial Apps Script function (runCloudJob) is called. It makes the authenticated API call to execute the Cloud Run Job. The API response contains the execution’s name (e.g., projects/your-project/locations/us-central1/executions/my-job-abcde-12345). This name is immediately stored using PropertiesService, a key-value store scoped to the script.

  2. Create the Trigger: Immediately after storing the execution name, the same function programmatically creates a new time-driven trigger. This trigger is configured to run a dedicated checkJobStatus function every minute.

  3. The Polling Function (checkJobStatus): This function is the heart of our feedback loop. Every time the trigger fires, it executes the following logic:

  • It retrieves the stored execution name from PropertiesService.

  • It makes an authenticated GET request to the Cloud Run Admin API endpoint for that specific execution (https://run.googleapis.com/v1/{name}).

  • It parses the JSON response to find the conditions array and checks the status of the final Completed condition.

  • If the job is Succeeded or Failed, it calls our final function to write the results back to the Doc, retrieves the trigger’s unique ID, and—this is critical—deletes the trigger to stop the polling.

  • If the job is still running, the function does nothing, and the trigger will simply fire again after the next interval.

Here is a conceptual code snippet illustrating the creation of the trigger and the checker function:


// In your initial function that starts the job...

function startJobAndCreateTrigger(jobExecutionName) {

const properties = PropertiesService.getDocumentProperties();

properties.setProperty('JOB_EXECUTION_NAME', jobExecutionName);

// Create a trigger that will run our checker function every 2 minutes.

ScriptApp.newTrigger('checkJobStatus')

.timeBased()

.everyMinutes(2)

.create();

// Let the user know the polling has started

DocumentApp.getUi().alert('Job started! The document will be updated upon completion.');

}

// The function that the trigger will execute

function checkJobStatus() {

const properties = PropertiesService.getDocumentProperties();

const jobExecutionName = properties.getProperty('JOB_EXECUTION_NAME');

if (!jobExecutionName) {

// Job name not found, maybe an error occurred. Clean up triggers.

deleteTriggers_();

return;

}

// Construct the API URL for the specific job execution

const apiUrl = `https://run.googleapis.com/v1/${jobExecutionName}`;

const options = {

method: 'get',

headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },

muteHttpExceptions: true

};

const response = UrlFetchApp.fetch(apiUrl, options);

const result = JSON.parse(response.getContentText());

// Find the 'Completed' condition in the status object

const completedCondition = result.status.conditions.find(c => c.type === 'Completed');

if (completedCondition && completedCondition.status === 'True') {

// Job is finished!

const finalStatus = completedCondition.reason === 'Completed' ? 'Succeeded' : 'Failed';

const message = completedCondition.message || 'No message provided.';

// Write the result back to the doc

writeResultsToDoc(finalStatus, message);

// CRITICAL: Clean up properties and triggers

properties.deleteProperty('JOB_EXECUTION_NAME');

deleteTriggers_();

} else {

// Job is still running, do nothing. The trigger will fire again.

console.log('Job is still in progress...');

}

}

function deleteTriggers_() {

const allTriggers = ScriptApp.getProjectTriggers();

for (const trigger of allTriggers) {

if (trigger.getHandlerFunction() === 'checkJobStatus') {

ScriptApp.deleteTrigger(trigger);

}

}

}

This cleanup step is non-negotiable. Failing to delete the trigger will result in orphaned processes that continue to run and query the API long after the job is complete, consuming quotas and potentially causing errors.

Writing the final status and output back into the original Google Doc

The final piece of the puzzle is delivering the results to the user. When our checkJobStatus function detects a completed job, it needs to modify the Google Doc. Using the DocumentApp service, this is remarkably straightforward.

A robust strategy is to have a predefined placeholder in your document template, something like [JOB_OUTPUT_HERE]. Your script can then search for this text and replace it with a formatted summary of the job’s execution.

The update function would perform these steps:

  1. Get the body of the active document using DocumentApp.getActiveDocument().getBody().

  2. Use the findText() and replaceText() methods to locate your placeholder.

  3. Construct a formatted string containing the final status, a timestamp, and any relevant output or error messages returned by the Cloud Run API.

  4. Insert this string into the document.

Here’s what the writeResultsToDoc function might look like:


/**

* Writes the final job status and message back into the Google Doc.

* Replaces a placeholder string '[JOB_OUTPUT_HERE]' in the document.

*/

function writeResultsToDoc(status, message) {

const doc = DocumentApp.getActiveDocument();

const body = doc.getBody();

const now = new Date().toISOString();

let outputText = `

## Cloud Run Job Results ##

**Completion Time:** ${now}

**Final Status:** ${status}

**Details:**

${message}

`;

// Find and replace the placeholder text with our formatted output

const placeholder = '[JOB_OUTPUT_HERE]';

const foundElement = body.findText(placeholder);

if (foundElement) {

// If the placeholder is found, replace it

foundElement.getElement().asText().replaceText(placeholder, outputText);

} else {

// If no placeholder, just append the results to the end of the document

body.appendParagraph("").setText(outputText);

}

}

With this final step, the loop is closed. The user clicks a button, a powerful backend job runs in the cloud, and the result appears seamlessly within the same document minutes later. The Google Doc has successfully served as the user interface for a complex, asynchronous cloud workflow.

Benefits and Scalability of the WaaS Pattern

Adopting the Workspace as a Service (WaaS) pattern is more than just a novel integration; it’s a strategic decision that unlocks significant operational efficiencies and enhances collaboration between technical and non-technical teams. By moving the user interface for complex backend tasks into a familiar, ubiquitous tool like Google Docs, you create a powerful, scalable, and surprisingly robust system. Let’s dissect the key benefits that make this pattern so compelling.

Empowering business teams while maintaining architectural control

The primary value proposition of this pattern lies in its ability to perfectly balance user empowerment with centralized engineering control. It resolves the classic tension between business agility and technical governance.

On one hand, you empower your business users. Teams in marketing, finance, or data analysis can now execute sophisticated, automated workflows on their own terms. They don’t need to learn a new web UI, master a command-line interface, or file a ticket with the engineering team and wait. The interface is Google Docs—a tool they already use daily.

Consider a scenario where the marketing team needs to generate custom audience segmentation reports. Instead of a multi-step manual process or a request to a data engineer, they simply open a templated Google Doc, fill in parameters like date_range, customer_region, and campaign_id, and select “Generate Report” from a custom menu. This action triggers a Cloud Run service that performs the heavy lifting and deposits the result in a designated GCS bucket. The turnaround time for their request drops from days to minutes, fostering a culture of self-service and data-driven decision-making.

On the other hand, the engineering team retains complete architectural control. The Google Doc is merely a structured input mechanism—a user-friendly front-end. The core business logic, security, and operational complexity are encapsulated within your version-controlled, containerized Cloud Run service. You define the “API contract” through the document template. You control:

  • The Logic: The container image dictates exactly what happens when the service is called.

  • The Inputs: Your code parses and validates the data from the Google Doc, rejecting any malformed requests.

  • The Environment: The service runs in a secure, managed GCP environment with defined resources and permissions.

This model provides the best of both worlds: a frictionless user experience for business teams and a standardized, secure, and maintainable backend for engineering.

Creating an inherent audit trail for every request

In any automated system, the ability to answer “Who did what, and when?” is critical for debugging, compliance, and security. The WaaS pattern provides a powerful and inherent audit trail with minimal extra effort, leveraging the native capabilities of Automated Discount Code Management System.

Every time a user triggers an action from a Google Doc, a multi-layered audit trail is automatically generated:

  1. Google Docs Version History: This is your first layer of evidence. You can see exactly who edited the document and precisely what the “payload” (the content of the doc) looked like at the moment the Apps Script was executed. This is invaluable for reproducing issues, as you have a perfect snapshot of the input parameters.

  2. Automated Email Journey with Google Sheets and Google Analytics Logs: For enterprises, Automated Google Slides Generation with Text Replacement audit logs can provide further details on document access and activity.

  3. Cloud Run Logs: Your backend service should, as a best practice, log key information upon invocation. By passing the documentId and the user’s email from the Apps Script to your Cloud Run service, you can create structured logs in Cloud Logging that explicitly link a backend execution to a specific user action in a specific document.

Correlating these logs gives you a complete, end-to-end trace of every request. If a job produces an unexpected result, you can instantly trace it back from the Cloud Run execution log to the exact version of the Google Doc that triggered it, see who made the last change, and analyze the parameters they provided. This built-in transparency is a massive operational advantage over opaque automation scripts or manual processes.

Considerations for security, IAM permissions, and error handling

While this pattern is powerful, its implementation demands a rigorous approach to security and user experience. A poorly secured or brittle setup can negate all its benefits.

Security and IAM Permissions:

The principle of least privilege must be your guiding star.

  • Apps Script Scopes: Be surgical with your OAuth scopes in the appsscript.json manifest. If your script only needs to read the current document, use https://www.googleapis.com/auth/documents.currentonly, not a broader Drive scope. For making the outbound call, use https://www.googleapis.com/auth/script.external_request. Every permission you request is a potential security liability.

  • Cloud Run Invocation: Never expose your Cloud Run service to the public internet. Configure its ingress settings to “Allow internal traffic and traffic from Cloud Load Balancing.” The recommended and most secure way to invoke it from Apps Script is via an authenticated proxy, such as a 2nd Gen Cloud Function with an HTTP trigger. The Apps Script can generate a Google-signed identity token for the user or a service account and include it in the Authorization header. The Cloud Function then validates this token before securely invoking the internal Cloud Run service.

  • Service Account Permissions: The service account attached to your Cloud Run service must also have the absolute minimum IAM permissions required for its task. If it only needs to read from one GCS bucket and write to a specific BigQuery table, grant it roles for only those actions on only those resources.

Error Handling and User Feedback:

A silent failure is a usability disaster. Your system must be resilient and communicate its state clearly to the user within the Google Doc.

  • Backend Robustness: Your Cloud Run service should perform strict validation on the input it receives from the document. If a required field is missing or a date is malformed, it should fail gracefully and return a structured error response (e.g., a JSON object with an error key and a human-readable message).

  • Frontend Communication: The Apps Script code must be wrapped in try...catch blocks. When the UrlFetchApp call to your backend fails or returns an error status code, the catch block should parse the response and display a clear, actionable message to the user using SpreadsheetApp.getUi().alert() or a toast notification. Instead of “Error 500,” show “Failed: The ‘start_date’ you provided is not a valid date. Please correct it and try again.”

  • Handling Asynchronicity: For processes that take more than a few seconds, provide status feedback. A simple approach is for the script to immediately write “Status: Processing…” into a specific cell or footer of the document. For truly long-running jobs, you might implement a more complex pattern where the Cloud Run service publishes a completion message to a Pub/Sub topic, which triggers a Cloud Function that uses the Google Docs API to update the document’s status to “Complete” or “Failed,” including a link to the output.

Conclusion: Your Next Step to Scalable Architecture

We’ve journeyed from a simple concept—a Google Doc—to a fully automated deployment pipeline on Google Cloud Run. This exploration demonstrates a fundamental shift in how we can approach operational workflows. By moving the control plane into a familiar, collaborative environment, we don’t just build a clever integration; we create a new paradigm for interaction with complex cloud infrastructure. The barrier to entry is lowered, visibility is increased, and the entire process becomes more intuitive and auditable.

Recap: The power of integrating Automated Order Processing Wordpress to Gmail to Google Sheets to Jobber with GCP

The core strength of the Workspace as a Service pattern lies in its fusion of two powerful ecosystems. On one side, you have Automated Payment Transaction Ledger with Google Sheets and PayPal: ubiquitous, user-friendly, and built for collaboration. On the other, Google Cloud Platform: a robust, scalable, and secure foundation for modern applications.

By bridging these two worlds, we achieved several key outcomes:

  • Democratized Deployments: Technical and non-technical stakeholders can initiate and monitor deployments using tools they already know, like Google Docs or Sheets.

  • Inherent Audit Trail: The revision history of a Google Doc serves as an immutable, timestamped log of every change and deployment request.

  • Simplified Workflow: We replaced complex CLI commands or CI/CD dashboards with a simple, document-driven process, reducing cognitive load and the potential for human error.

  • Leveraged Existing Security: The entire workflow is governed by Google’s robust identity and access management, using familiar sharing permissions to control who can trigger actions.

This isn’t just about convenience; it’s about building more resilient, transparent, and human-centric systems.

How this pattern can be adapted for other use cases

The Google Doc-to-Cloud Run pipeline is just one implementation of this versatile pattern. The underlying principle—using a Workspace event to trigger a GCP action—can be adapted to solve a multitude of challenges across your organization.

Consider these potential applications:

  • Data Pipeline Management: Use a Google Sheet as a manifest for a batch processing job. Adding a new row with a Cloud Storage file path could trigger a Cloud Function that starts a Dataflow job to process that specific file.

  • On-demand Environment Provisioning: A manager could approve a request for a temporary staging environment by adding their “LGTM” to a specific cell in a Google Sheet. An Apps Script trigger could then call a Cloud Run service that executes a Terraform plan to spin up the required infrastructure.

  • Dynamic Content Management: Treat a Google Drive folder as a headless CMS. When a writer moves a finalized Google Doc into a “Published” folder, a webhook can trigger a service to convert the document to HTML, push it to a static hosting bucket, and invalidate the CDN cache.

  • Streamline Incident Response Automate Jira PagerDuty Alerts from Google Chat: A new incident can be declared by creating a Google Doc from a “Postmortem” template. The act of creation could trigger a Cloud Function to automatically create a Jira ticket, open a dedicated Slack channel, and add the on-call team to the document.

The possibilities are limited only by the APIs that Google Docs to Web and GCP expose. Any manual, repetitive process that can be described or cataloged in a document or spreadsheet is a prime candidate for this type of automation.

Ready to scale your architecture? Book a GDE discovery call

Implementing a pattern like this requires careful consideration of security, scalability, and your specific organizational needs. While this article provides a blueprint, every real-world application has its unique complexities.

If you’re ready to explore how this or other advanced cloud-native patterns can transform your workflows, let’s talk. A discovery call with a Google Developer Expert (GDE) can help you validate your architecture, identify potential pitfalls, and chart a clear path forward. We can dive deep into your specific challenges and co-design a solution that is secure, efficient, and built to scale.

Book your complimentary GDE discovery call today and take the next definitive step in your cloud automation journey.


Tags

GCPCloud RunGoogle WorkspaceAutomationServerlessDevOpsPlatform Engineering

Share


Previous Article
Automating GCP IAM Audits with Gemini to Stop Privilege Creep
Vo Tu Duc

Vo Tu Duc

A Google Developer Expert, Google Cloud Innovator

Stop Doing Manual Work. Scale with AI.

Hi, I'm Vo Tu Duc (Danny), a recognised Google Developer Expert (GDE). I architect custom AI agents and Google Workspace solutions that help businesses eliminate chaos and save thousands of hours.

Want to turn these blog concepts into production-ready reality for your team?
Book a Discovery Call

Table Of Contents

1
The Challenge: The Operational Bottleneck in Self-Service Tooling
2
High-Level Architectural Overview
3
Step 1: Designing the Request Interface in Google Docs
4
Step 2 Orchestrating the API Call with Apps Script
5
Step 3: Executing the Task with a GCP Cloud Run Job
6
Step 4: Closing the Loop: Asynchronous Feedback to the User
7
Benefits and Scalability of the WaaS Pattern
8
Conclusion: Your Next Step to Scalable Architecture

Portfolios

AI Agentic Workflows
Cloud Engineering
AppSheet Solutions
Change Management
Strategy Playbooks
Product Showcase
Uncategorized
Workspace Automation

Related Posts

Automate Site Defect Punch Lists with Gemini and Google Chat
May 22, 2026
© 2026, All Rights Reserved.
Powered By

Quick Links

Book a CallAbout MeVolunteer Legacy

Social Media