- Hetzner Cloud — Create account, generate API token at https://console.hetzner.cloud
- Cloudflare — Add your domain, note Zone ID and Account ID from the dashboard
- Cloudflare API Token — Create at https://dash.cloudflare.com/profile/api-tokens with permissions:
- Zone → DNS → Edit
- Account → Access: Apps and Policies → Edit
- Stripe — Create account, get Secret Key from https://dashboard.stripe.com/apikeys
- Stripe Product — Create a $20/month recurring product, copy the Price ID (
price_xxx) - Stripe Webhook — Add endpoint
https://admin.<your-domain>/webhookwith events:checkout.session.completedcustomer.subscription.deletedinvoice.payment_failed
- Copy the Webhook Signing Secret (
whsec_xxx)
- Domain registered and nameservers pointed to Cloudflare
- Verify domain is active in Cloudflare dashboard (status: Active)
- Terraform will create A records automatically (root, admin, wildcard)
- Generate a deploy key if you don't have one:
ssh-keygen -t ed25519 -f ~/.ssh/openclaw_deploy -C "openclaw-deploy"
- Note the path — you'll use it in
terraform.tfvars
cd infra
cp terraform.tfvars.example terraform.tfvars
# Fill in all values in terraform.tfvars
terraform init
terraform plan # Review what will be created
terraform apply # Create server + DNS records-
terraform applycompletes without errors - Note the outputs:
terraform output server_ip terraform output -raw admin_api_key terraform output ssh_command
Cloud-init runs automatically on first boot (~5-10 minutes). Monitor progress:
ssh root@<server-ip>
tail -f /var/log/cloud-init-output.log- Cloud-init finishes (check
/var/log/openclaw-setup.log) - Docker is running:
docker ps - openclaw-desktop image is built:
docker images | grep openclaw - Caddy is running:
docker ps | grep caddy - Webhook service is running:
docker ps | grep webhook
Cloud-init doesn't have your Stripe keys (they aren't in Terraform). SSH in and add them:
ssh root@<server-ip>
vi /opt/openclaw/.env
# Add: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_ID
cd /opt/openclaw
docker compose -f docker-compose.webhook.yml --env-file .env up -d- Stripe env vars are set in
/opt/openclaw/.env - Webhook service restarted with Stripe keys
- Landing page loads:
https://admin.<your-domain> - HTTPS certificate is valid (Caddy auto-provisions via Let's Encrypt)
- Admin API responds:
curl -H "Authorization: Bearer <admin-api-key>" \ https://admin.<your-domain>/admin/customers
Use Stripe test mode first:
- Click "Subscribe" on the landing page
- Complete checkout with test card
4242 4242 4242 4242 - Webhook receives
checkout.session.completed - Container is provisioned (check
docker ps) - DNS record is created in Cloudflare
- Cloudflare Access policy is created
- Success page shows the desktop URL
- Desktop is accessible at
https://<subdomain>.<your-domain> - Google login prompt appears (Cloudflare Access)
- Cancel the test subscription in Stripe dashboard
- Webhook receives
customer.subscription.deleted - Container is removed
- DNS record is deleted
- Access policy is removed
- Switch Stripe to live mode — update keys in
/opt/openclaw/.env - Create live webhook endpoint in Stripe (same URL, same events)
- Update
STRIPE_WEBHOOK_SECRETwith the live signing secret - Restart webhook service:
cd /opt/openclaw && docker compose -f docker-compose.webhook.yml --env-file .env up -d
- Do one real purchase to verify end-to-end
- Backups are running nightly (check
/var/log/openclaw-backup.log) - Set up monitoring/alerting (uptime check on
https://admin.<your-domain>) - To update the platform:
ssh root@<server-ip> cd /opt/openclaw/repo && git pull docker build -t openclaw-desktop:latest . /opt/openclaw/scripts/rolling_update.sh