One-command deployment of an OpenClaw AI agent on DigitalOcean, Azure VM, GCP VM, or GCP Cloud Run with Telegram and Slack support.
- Telegram bot with DM and group chat support
- Slack bot support (Socket Mode)
- Web search via Brave Search (falls back to DuckDuckGo)
- 8 switchable free LLM models via
/model <alias> - Secrets managed via
.env— never committed
- Terraform >= 1.5
- direnv (
brew install direnv) - SSH key pair
- DigitalOcean account + API token (for DO path)
- Azure subscription + service principal credentials (for Azure path)
- GCP project (for GCP VM or GCP Cloud Run path)
- OpenRouter API key
- Telegram bot token (from @BotFather)
- Slack App-Level token (starts with
xapp-) - Slack Bot User OAuth token (starts with
xoxb-)
cp .env.example .envEdit .env and fill in your values:
| Variable | Description |
|---|---|
OPENROUTER_API_KEY |
From openrouter.ai/keys |
TELEGRAM_BOT_TOKEN |
From @BotFather |
OPENCLAW_GATEWAY_TOKEN |
Any strong random string |
BRAVE_API_KEY |
From api.search.brave.com — optional, falls back to DuckDuckGo |
TELEGRAM_OWNER_ID |
Your Telegram user ID from @userinfobot — grants /model and other privileged commands |
SLACK_APP_TOKEN |
Slack App-Level token (starts with xapp-) |
SLACK_BOT_TOKEN |
Slack Bot User OAuth token (starts with xoxb-) |
cd terraform/digitalOceanEdit terraform.tfvars to set your DigitalOcean token and optionally adjust region, droplet size, and swap:
do_token = "dop_v1_..."
ssh_public_key_path = "~/.ssh/id_rsa.pub"
region = "tor1" # tor1, sfo3, nyc3, sgp1, ams3, ...
droplet_size = "s-1vcpu-1gb" # $6/mo — increase if OOM
swap_size = "3G"cd terraform/azure_vmCreate terraform.tfvars and set your Azure + VM values:
subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_secret = "..."
resource_group_name = "your-existing-rg"
location = "eastus"
ssh_public_key_path = "~/.ssh/id_rsa.pub"
vm_name = "openclaw-b2pts"
vm_size = "Standard_B2pts_v2"
os_disk_size_gb = 30
swap_size = 2
openclaw_memory_limit_mb = 800cd terraform/gcp_vm
cp terraform.tfvars.example terraform.tfvarsEdit terraform.tfvars:
project_id = "your-gcp-project-id"
region = "us-west1"
zone = "us-west1-b"
vm_name = "openclaw-e2-micro"
machine_type = "e2-micro"
boot_disk_size_gb = 30
admin_username = "openclaw"
ssh_public_key_path = "~/.ssh/id_rsa.pub"
network_name = "default"
# Replace with your public IP/CIDR
ssh_allowed_cidrs = ["203.0.113.10/32"]
gateway_allowed_cidrs = ["203.0.113.10/32"]
swap_size = 3
openclaw_memory_limit_mb = 800GCP VM deployment in this repo uses:
- Compute Engine VM (default
e2-micro) - 30GB boot disk
- optional swap + systemd memory cap for OpenClaw process
- firewall rules for SSH (
22) and OpenClaw gateway (18789)
cd terraform/gcp_cloudrun
cp terraform.tfvars.example terraform.tfvarsEdit terraform.tfvars:
project_id = "your-gcp-project-id"
region = "us-west1"
service_name = "openclaw"
min_instances = 1
max_instances = 3
ghcr_remote_repository_id = "ghcr-remote"
ghcr_image_path = "openclaw/openclaw"
ghcr_image_tag = "latest"
bucket_name = "your-gcp-project-id-openclaw-state"GCP Cloud Run deployment in this repo uses:
- Cloud Run service (managed runtime, no VM SSH needed)
- Artifact Registry remote repo proxy for GHCR images
- Secret Manager for runtime secrets
- GCS bucket for persistent state
# First time only
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && source ~/.zshrc
direnv allowterraform init # first time only
terraform applyWait ~5 minutes for bootstrap to complete. The bot will start automatically.
For VM targets (DigitalOcean / Azure VM / GCP VM):
terraform output ssh_commandFor GCP Cloud Run target:
terraform output cloud_run_urlThen:
- VM targets: SSH to the VM using the output command.
- Cloud Run target: open the Cloud Run URL from output to confirm the service is reachable.
- Send a Telegram message to confirm bot response.
- (Optional) send a Slack message if Slack tokens are configured.
In Telegram, use /model <alias>:
All models are free tier on OpenRouter (rate limits apply).
ssh_allowed_cidrsandgateway_allowed_cidrsdefault to open (0.0.0.0/0). For production, restrict to your IP interraform.tfvars:
ssh_allowed_cidrs = ["203.0.113.10/32"]
gateway_allowed_cidrs = ["203.0.113.10/32"]- CI security checks are defined in .github/workflows/security.yml (ShellCheck, .envrc policy, Checkov, Gitleaks).