Browser-accessible cloud desktops with OpenClaw pre-installed. Each customer gets an isolated KasmVNC container behind HTTPS, provisioned automatically via Stripe.
Customer browser
│
▼
Caddy (HTTPS, auto-certs)
│
├── alice.example.com → openclaw-alice:6901
├── bob.example.com → openclaw-bob:6901
└── admin.example.com → openclaw-webhook:5000
- Base image: KasmVNC Ubuntu desktop + OpenClaw (
Dockerfile) - Reverse proxy: Caddy with Docker label discovery — auto-HTTPS per subdomain
- Isolation: Each customer runs on its own Docker network
- Persistence: Named volumes at
/home/kasm-usersurvive restarts and updates - Billing: Stripe Checkout → webhook → auto-provision/deprovision
Run a single desktop container locally — no Stripe, Cloudflare, or domain needed.
# Build and start
docker compose up --build
# Open in browser
open http://localhost:6901Log in with password changeme. This uses docker-compose.yml which runs one container with ports exposed directly.
To test the full signup flow without deploying:
# 1. Install the Stripe CLI: https://docs.stripe.com/stripe-cli
# 2. Start the webhook service
cp .env.example .env
# Fill in STRIPE_SECRET_KEY, STRIPE_PRICE_ID (test mode keys)
docker network create openclaw_net
docker compose -f docker-compose.webhook.yml up --build
# 3. Forward Stripe events to your local webhook
stripe listen --forward-to localhost:5000/webhook
# Copy the webhook signing secret (whsec_...) into .env as STRIPE_WEBHOOK_SECRET
# Restart the webhook service
# 4. Open http://localhost:5000 and test the checkout flow- Docker + Docker Compose
jqinstalled on the host- A domain with DNS on Cloudflare
- A Stripe account with a Product/Price created
cp .env.example .env
# Fill in: SERVER_IP, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID,
# STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_ID,
# ADMIN_API_KEYdocker compose build# Start Caddy reverse proxy
docker compose -f docker-compose.caddy.yml up -d
# Start the signup/webhook service
docker compose -f docker-compose.webhook.yml up -dIn the Stripe dashboard, add a webhook endpoint:
- URL:
https://admin.<your-domain>/webhook - Events:
checkout.session.completed,customer.subscription.deleted,invoice.payment_failed
| Event | What Happens |
|---|---|
Customer signs up at admin.example.com |
Picks subdomain → Stripe Checkout |
| Payment succeeds | Webhook provisions container + DNS + DB record |
| Customer cancels subscription | Webhook deprovisions container + DNS cleanup |
| Payment fails | Status updated in DB, container stays running |
# Provision manually
./scripts/openclaw-admin provision alice
# List all customers
./scripts/openclaw-admin list
# Check container health
./scripts/openclaw-admin status
./scripts/openclaw-admin status alice
# Look up a customer record
./scripts/openclaw-admin db-get alice
# Deprovision (removes container, volume, DNS)
./scripts/openclaw-admin deprovision alice
# Deprovision but keep data
./scripts/openclaw-admin deprovision --keep-data alice
# Back up all customer volumes
./scripts/openclaw-admin backup
# Rolling update to a new image
./scripts/openclaw-admin update
./scripts/openclaw-admin update openclaw-desktop:v2The webhook service exposes authenticated endpoints at admin.<your-domain>:
# List customers
curl -H "Authorization: Bearer $ADMIN_API_KEY" https://admin.example.com/admin/customers
# Provision via API
curl -X POST -H "Authorization: Bearer $ADMIN_API_KEY" \
-d '{"subdomain":"alice"}' https://admin.example.com/admin/provision
# Deprovision via API
curl -X POST -H "Authorization: Bearer $ADMIN_API_KEY" \
-d '{"subdomain":"alice"}' https://admin.example.com/admin/deprovisionAutomated daily backups via cron:
# Add to crontab
0 3 * * * /path/to/scripts/backup_all.sh >> /var/log/openclaw-backup.log 2>&1Backups are compressed tarballs stored in $BACKUP_DIR (default: ./data/backups/). Old backups are pruned after $BACKUP_RETENTION_DAYS days (default: 7).
| Resource | Default | Env Var |
|---|---|---|
| CPU | 1 core | CPUS |
| Memory | 2 GB | MEMORY |
| Shared memory | 1 GB | SHM_SIZE |
At 2 GB per customer, a 64 GB server supports ~25 concurrent customers.
├── Dockerfile # KasmVNC + OpenClaw image
├── docker-compose.yml # Dev/test
├── docker-compose.caddy.yml # Caddy reverse proxy
├── docker-compose.webhook.yml # Stripe webhook service
├── .env.example # Environment variable template
├── scripts/
│ ├── provision_customer.sh # Provision container + DNS + DB
│ ├── deprovision_customer.sh # Tear down container + DNS + DB
│ ├── backup_all.sh # Volume backups
│ ├── rolling_update.sh # Image update across all containers
│ ├── openclaw-admin # Admin CLI
│ └── lib/
│ ├── db.sh # Customer JSON database helpers
│ └── cloudflare.sh # Cloudflare DNS API helpers
├── webhook/
│ ├── Dockerfile # Python Flask service
│ ├── app.py # Webhook handler + signup flow
│ ├── requirements.txt
│ └── templates/
│ ├── index.html # Landing/signup page
│ └── success.html # Post-checkout credentials
└── data/
└── customers.json # Customer database (created at runtime)
