1. Introduction
Overview
In this lab, you will deploy a fully persistent, production-grade instance of OpenClaw—an open-source AI agent framework—to Cloud Run Instances using declarative Infrastructure as Code (Terraform). You will provision Cloud Run Instances using the google_cloud_run_v2_instance resource from the google provider, mount Google Cloud Storage via Cloud Storage FUSE to persist agent workspace state, manage API credentials securely using Google Cloud Secret Manager, and interact with your AI agent through OpenClaw's built-in Web UI using Cloud Run Instances native SSH for secure device pairing.
Before getting started, you can explore the OpenClaw Documentation to familiarize yourself with OpenClaw's architecture, tools, and agent workflows.
What you'll do
- Define your entire infrastructure declaratively in Terraform (
main.tf), including APIs, service accounts, IAM bindings, secrets, storage buckets, and Cloud Run Instances. - Deploy OpenClaw 2.0+ (
ghcr.io/openclaw/openclaw:latest) usinggoogle_cloud_run_v2_instance. - Automatically generate and store secure gateway credentials in Secret Manager.
- Mount a Cloud Storage bucket via Cloud Storage FUSE and synchronize
/home/node/.openclawfor durable state, workspace, and SQLite persistence. - Use
gcloud beta run instances sshto authenticate and approve browser device pairing requests into the OpenClaw Control Web UI. - (Optional) Configure a messaging channel (Telegram or WhatsApp).
- (Optional) Extend your agent's capabilities by adding custom Skills to Cloud Storage.
What you'll learn
- How to declare and deploy Cloud Run Instances using Terraform.
- How to configure Cloud Storage FUSE volume mounts in Terraform.
- How to securely inject Secret Manager secrets as environment variables into Cloud Run Instances.
- How to manage OpenClaw 2.0 device authentication using Cloud Run Instances native SSH.
- How to run long-running, persistent agent workloads on Cloud Run Instances.
2. Setup and Requirements
GCP Project Setup
- Sign in to the Google Cloud Console.
- Create or select a Google Cloud Project.
- Ensure billing is enabled for your Google Cloud project.
Open Cloud Shell
Activate Google Cloud Shell from the top toolbar of the Cloud Console. Cloud Shell includes Terraform and the Google Cloud SDK pre-installed.
Set Project & Region
Set your project ID and region as environment variables and configure gcloud:
export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION=<YOUR_REGION>
gcloud config set project $PROJECT_ID
gcloud config get project
Install gcloud beta Components
Ensure the beta component is installed (required for Cloud Run Instances SSH and log streaming):
gcloud components install beta --quiet
gcloud components update
Verify Terraform
Confirm that Terraform is installed and meets version requirements:
terraform version
You should see Terraform v1.5.0 or higher.
3. (Optional) Set Up Messaging Integrations (Telegram or WhatsApp)
You can connect OpenClaw to Telegram or WhatsApp. Choose Option A or Option B below.
Option A: Telegram Bot Setup
- Open Telegram and search for
@BotFather. - Send the
/newbotcommand and follow the prompts to specify a bot name and username. - Copy the generated HTTP API Token (e.g.,
123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ). - Search for
@userinfoboton Telegram, send/start, and copy your numeric User ID (e.g.,8035936176).
You will supply these values as Terraform input variables in your terraform.tfvars file.
Option B: WhatsApp Setup
- Obtain your personal WhatsApp phone number in international format without spaces or symbols (e.g.,
+15551234567). - OpenClaw connects via the WhatsApp channel allowlist policy.
4. Define Infrastructure in Terraform
We will declare our entire infrastructure in Terraform. This includes enabling required Google Cloud APIs, creating the dedicated service account and IAM roles, generating and storing secrets in Secret Manager, provisioning the Cloud Storage state bucket, generating the openclaw.json configuration, and deploying the Cloud Run Instance.
1. Create a Terraform Directory
In Cloud Shell, create a directory for your Terraform files and navigate into it:
mkdir openclaw-terraform && cd openclaw-terraform
2. Create main.tf
Create a file named main.tf. This configuration uses the Google provider to define all infrastructure components:
terraform {
required_version = ">= 1.5.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 8.8.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# 1. Enable Required APIs
resource "google_project_service" "services" {
for_each = toset([
"run.googleapis.com",
"secretmanager.googleapis.com",
"storage.googleapis.com",
"generativelanguage.googleapis.com",
"compute.googleapis.com"
])
project = var.project_id
service = each.key
disable_on_destroy = false
}
# 2. Dedicated Service Account
resource "google_service_account" "openclaw_sa" {
account_id = "openclaw-sa"
display_name = "OpenClaw Service Account"
depends_on = [google_project_service.services]
}
# 3. Secret Manager: Gateway Password
resource "random_password" "gateway_password" {
length = 32
special = false
}
resource "google_secret_manager_secret" "gateway_password" {
secret_id = "openclaw-gateway-password"
replication {
auto {}
}
depends_on = [google_project_service.services]
}
resource "google_secret_manager_secret_version" "gateway_password" {
secret = google_secret_manager_secret.gateway_password.id
secret_data = random_password.gateway_password.result
}
resource "google_secret_manager_secret_iam_member" "sa_gateway_password" {
secret_id = google_secret_manager_secret.gateway_password.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.openclaw_sa.email}"
}
# 4. Secret Manager: Gemini API Key
resource "google_secret_manager_secret" "gemini_api_key" {
secret_id = "gemini-api-key"
replication {
auto {}
}
depends_on = [google_project_service.services]
}
resource "google_secret_manager_secret_version" "gemini_api_key" {
secret = google_secret_manager_secret.gemini_api_key.id
secret_data = var.gemini_api_key
}
resource "google_secret_manager_secret_iam_member" "sa_gemini_api_key" {
secret_id = google_secret_manager_secret.gemini_api_key.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.openclaw_sa.email}"
}
# 5. Optional Secret Manager: Telegram Bot Token
resource "google_secret_manager_secret" "telegram_bot_token" {
count = var.telegram_bot_token != "" ? 1 : 0
secret_id = "telegram-bot-token"
replication {
auto {}
}
depends_on = [google_project_service.services]
}
resource "google_secret_manager_secret_version" "telegram_bot_token" {
count = var.telegram_bot_token != "" ? 1 : 0
secret = google_secret_manager_secret.telegram_bot_token[0].id
secret_data = var.telegram_bot_token
}
resource "google_secret_manager_secret_iam_member" "sa_telegram_bot_token" {
count = var.telegram_bot_token != "" ? 1 : 0
secret_id = google_secret_manager_secret.telegram_bot_token[0].id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.openclaw_sa.email}"
}
# 6. Cloud Storage Bucket for State Persistence
resource "google_storage_bucket" "openclaw_state" {
name = "openclaw-state-${var.project_id}"
location = var.region
uniform_bucket_level_access = true
force_destroy = true
depends_on = [google_project_service.services]
}
resource "google_storage_bucket_iam_member" "sa_storage" {
bucket = google_storage_bucket.openclaw_state.name
role = "roles/storage.objectUser"
member = "serviceAccount:${google_service_account.openclaw_sa.email}"
}
# 7. Upload openclaw.json to Cloud Storage Root
resource "google_storage_bucket_object" "openclaw_config" {
name = "openclaw.json"
bucket = google_storage_bucket.openclaw_state.name
content = jsonencode({
gateway = {
mode = "local"
port = 18789
trustedProxies = ["127.0.0.1", "::1", "169.254.0.0/16", "10.0.0.0/8"]
bind = "lan"
auth = {
password = "$${OPENCLAW_GATEWAY_PASSWORD}"
}
controlUi = {
allowedOrigins = ["*"]
enabled = true
}
}
memory = {
search = {
provider = "gemini"
}
}
agents = {
defaults = {
model = {
primary = "google/gemini-3.1-pro-preview"
}
sandbox = {
mode = "off"
}
}
}
skills = {
load = {
extraDirs = ["/mnt/openclaw/skills"]
}
}
channels = {
telegram = {
enabled = var.telegram_bot_token != ""
defaultAccount = "default"
accounts = {
default = {
enabled = var.telegram_bot_token != ""
dmPolicy = "allowlist"
allowFrom = var.telegram_user_id != "" ? [var.telegram_user_id] : []
}
}
}
}
plugins = {
entries = {
google = { enabled = true }
telegram = { enabled = var.telegram_bot_token != "" }
}
}
})
}
# 8. Cloud Run Instance
resource "google_cloud_run_v2_instance" "openclaw" {
name = "openclaw-instance"
location = var.region
ingress = "INGRESS_TRAFFIC_ALL"
invoker_iam_disabled = true
restart_policy = "ON_FAILURE"
service_account = google_service_account.openclaw_sa.email
containers {
image = "ghcr.io/openclaw/openclaw:latest"
args = [
"/bin/sh",
"-c",
"cp /mnt/openclaw/openclaw.json /home/node/.openclaw/ && exec node /app/openclaw.mjs gateway"
]
ports {
container_port = 18789
}
resources {
limits = {
cpu = "4"
memory = "4Gi"
}
}
env {
name = "GEMINI_API_KEY"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.gemini_api_key.secret_id
version = "latest"
}
}
}
env {
name = "OPENCLAW_GATEWAY_PASSWORD"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.gateway_password.secret_id
version = "latest"
}
}
}
dynamic "env" {
for_each = toset(var.telegram_bot_token != "" ? ["telegram"] : [])
content {
name = "TELEGRAM_BOT_TOKEN"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.telegram_bot_token[0].secret_id
version = "latest"
}
}
}
}
volume_mounts {
name = "openclaw-storage"
mount_path = "/mnt/openclaw"
}
}
volumes {
name = "openclaw-storage"
gcs {
bucket = google_storage_bucket.openclaw_state.name
read_only = false
mount_options = ["uid=1000", "gid=1000", "file-mode=0700", "dir-mode=0700"]
}
}
depends_on = [
google_project_service.services,
google_storage_bucket_object.openclaw_config,
google_secret_manager_secret_version.gateway_password,
google_secret_manager_secret_version.gemini_api_key,
google_secret_manager_secret_iam_member.sa_gateway_password,
google_secret_manager_secret_iam_member.sa_gemini_api_key,
google_storage_bucket_iam_member.sa_storage
]
}
3. Create variables.tf
Create a file named variables.tf to define the input variables:
variable "project_id" {
type = string
description = "Google Cloud Project ID"
}
variable "region" {
type = string
default = "us-west1"
description = "Google Cloud Region for Cloud Run and Storage"
}
variable "gemini_api_key" {
type = string
sensitive = true
description = "Gemini API Key from Google AI Studio"
}
variable "telegram_bot_token" {
type = string
default = ""
sensitive = true
description = "(Optional) Telegram Bot HTTP API Token"
}
variable "telegram_user_id" {
type = string
default = ""
description = "(Optional) Telegram User ID for allowlist policy"
}
4. Create outputs.tf
Create a file named outputs.tf to expose the deployed instance URL, generated gateway password, and storage bucket name:
output "instance_url" {
description = "Public URL for OpenClaw Cloud Run Instance"
value = google_cloud_run_v2_instance.openclaw.urls[0]
}
output "gateway_password" {
description = "Generated OpenClaw Gateway Password"
value = random_password.gateway_password.result
sensitive = true
}
output "storage_bucket" {
description = "Cloud Storage bucket persisting OpenClaw state"
value = google_storage_bucket.openclaw_state.name
}
5. Create terraform.tfvars
To obtain a Gemini API key:
- Visit Google AI Studio and sign in with your Google account.
- Click Create API key and select your Google Cloud project.
- Copy the generated API key.
Create a file named terraform.tfvars with your project coordinates:
project_id = "YOUR_PROJECT_ID"
region = "YOUR_REGION"
gemini_api_key = "YOUR_GEMINI_API_KEY"
# Optional: Uncomment if configuring Telegram
# telegram_bot_token = "YOUR_TELEGRAM_BOT_TOKEN"
# telegram_user_id = "YOUR_TELEGRAM_USER_ID"
5. Deploy Infrastructure with Terraform
1. Initialize and Apply
Initialize the Terraform working directory and download provider plugins:
terraform init
Deploy the infrastructure:
terraform apply
When prompted, review the planned resources and type yes to confirm.
2. Extract Deployment Outputs
Export the instance URL and generated gateway password from the Terraform state:
export INSTANCE_URL=$(terraform output -raw instance_url)
export GATEWAY_PASSWORD=$(terraform output -raw gateway_password)
echo "Instance URL: ${INSTANCE_URL}"
echo "Gateway Password: ${GATEWAY_PASSWORD}"
6. Interact via Web UI & Authenticate via Cloud Run SSH
OpenClaw 2.0+ enforces cryptographic device authentication. When connecting from a new browser, the gateway generates a pairing request that you approve directly through Cloud Run Instances native SSH.
1. Open the OpenClaw Control UI
- Open the
$INSTANCE_URLin your web browser. - When prompted for authentication, enter your gateway password (
$GATEWAY_PASSWORD). - Because this is a new browser session, the OpenClaw Control UI displays a pending device pairing prompt with a Request ID (for example:
b56143fc-1654-49da-a1d8-5d40479ddc66). Copy this Request ID.
2. SSH into the Cloud Run Instance
In Cloud Shell, start a secure interactive shell into your running instance:
gcloud beta run instances ssh openclaw-instance --region=${REGION}
3. Approve Device Pairing
Inside the SSH session, approve your browser request:
openclaw devices approve <REQUEST_ID> --password ${GATEWAY_PASSWORD}
4. Prompt Your Agent (Instances in Action)
Once approved, your browser immediately connects to the OpenClaw Control Dashboard!
- Test your OpenClaw agent by sending a prompt in the chat UI, such as,
"Hello". - To test the Cloud Run instance's long-lived lifecycle and persistent storage, prompt your OpenClaw agent to perform a scheduled, autonomous task. For example, have it monitor a GitHub repository for releases and draft a digest:
Check the GitHub repository openclaw/openclaw every hour for the next 24 hours. Track any new releases, merged pull requests, and breaking changes. Save a structured markdown digest titled `daily-release-digest.md` to your workspace, and notify me in this chat with a summary whenever a new release is detected. - Because OpenClaw state, workspace files, and SQLite databases are persisted directly to Cloud Storage via Cloud Storage FUSE, the scheduled task, conversation history, and generated digest files survive instance restarts and container upgrades.
7. (Optional) Verify Messaging Channel Integrations
If you configured Telegram or WhatsApp in the optional setup steps above, you can verify messaging delivery:
- Check instance logs in Cloud Shell:
gcloud beta run instances logs read openclaw-instance --region ${REGION} --limit 20 - Open Telegram (or WhatsApp) and send a message (e.g.
/startorHello OpenClaw!). - The bot will authenticate your user ID against the allowlist in
openclaw.jsonand respond using Gemini!
8. Monitor and Inspect Instance Logs
Cloud Run Instances captures stdout and stderr from your container and streams it directly to Google Cloud Logging. You can use Cloud Shell or the Cloud Console to monitor gateway traffic and inspect agent tool runs in real time.
1. Read Recent Instance Logs
To fetch recent log entries from the instance in Cloud Shell:
gcloud beta run instances logs read openclaw-instance \
--region ${REGION} \
--limit 50
2. Key Log Signatures to Observe
When observing your running agent, look for the following key runtime log events:
[gateway] ready: Confirms the OpenClaw HTTP and WebSocket gateway has initialized and is listening on port 18789.[gateway] agent model: ...: Displays the active Gemini model (e.g.google/gemini-3.1-pro-previeworgoogle/gemini-3.5-flash).[agents/tool-policy]: Shows the tools permitted or restricted under your policy.[agent/embedded]: Traces execution and tool invocation by the agent.
3. Inspect Logs in Google Cloud Console
You can also view and filter logs using the Logs Explorer in Cloud Console:
resource.type="cloud_run_instance"
resource.labels.instance_name="openclaw-instance"
9. (Optional) Extend Your Agent with Skills
OpenClaw supports Skills—modular capability packages that teach your agent specific workflows, specialized CLI tools, and domain-specific instructions.
How Skills Work
Every skill is a directory containing a SKILL.md file. It begins with YAML frontmatter specifying metadata (name and description), followed by Markdown instructions:
---
name: summarize-logs
description: Summarize Cloud Run error logs into actionable bullet points.
---
# Log Summarizer Skill
When asked to analyze or summarize logs:
1. Parse error stack traces and group similar errors by frequency.
2. Identify root causes such as memory limits, timeouts, or permission errors.
3. Propose concrete remediation steps.
name: The skill identifier (also callable directly as a slash command, for example/summarize-logs).description: Tells OpenClaw's model when to automatically invoke this skill in natural conversation.
Add a Custom Skill to Your Cloud Storage Bucket
Because your Cloud Storage bucket is mounted at /mnt/openclaw and configured in skills.load.extraDirs, skills placed under gs://${BUCKET_NAME}/skills/ are automatically loaded and persisted across instance restarts:
- Retrieve your Bucket Name:
export BUCKET_NAME=$(terraform output -raw storage_bucket) - Create a Local Skill Directory:
mkdir -p my-skill cat << 'EOF' > my-skill/SKILL.md --- name: summarize-logs description: Summarize Cloud Run error logs into actionable bullet points. --- # Log Summarizer Skill When asked to analyze or summarize logs, group errors by frequency and suggest actionable fixes. EOF - Upload the Skill to Cloud Storage:
gcloud storage cp -r my-skill gs://${BUCKET_NAME}/skills/ - Use the Skill in Chat:
- Return to the OpenClaw Control Web UI.
- You can now prompt your agent using
/summarize-logsor ask natural-language questions matching the skill description!
10. Clean Up
To avoid incurring ongoing charges to your Google Cloud account for the resources used in this codelab, you can destroy all provisioned infrastructure in a single step using Terraform:
- Ensure you are in your
openclaw-terraformdirectory:cd openclaw-terraform - Destroy all resources:
terraform destroy - When prompted, review the resources to be removed and type
yesto confirm. Terraform will cleanly delete the Cloud Run Instance, Secret Manager secrets, IAM policy bindings, service account, and the Cloud Storage bucket.
11. Conclusion
Congratulations! You have successfully deployed a secure, fully persistent instance of OpenClaw on Cloud Run Instances using declarative Terraform, backed by Cloud Storage state persistence, Secret Manager credential security, and Cloud Run Instances native SSH for secure device authentication!