All day-to-day operations go through make. Run source secrets/inputs.sh before any make target that touches the VPS.
Make Targets#
Infrastructure:
1
2
3
4
5
6
| make init # Initialize Terraform
make plan # Preview infrastructure changes
make apply # Apply infrastructure changes
make destroy # Destroy all infrastructure
make validate # Validate Terraform + config
make fmt # Format Terraform files
|
Deployment:
1
2
3
| make bootstrap # First-time setup (dirs, Docker build, config push, start)
make deploy # Push config/env to VPS and restart containers
make deploy REBUILD=1 # Also rebuild Docker images (use after docker/ changes)
|
If you are using Anthropic models, you can also set up subscription auth with:
1
| make setup-auth # Configure Claude subscription auth on VPS
|
Operations:
1
2
3
4
5
| make status # Container status, Tailscale, etc.
make logs # Stream Docker logs
make ssh # SSH to VPS as openclaw user
make tunnel # SSH tunnel to gateway (localhost:18789)
make exec CMD="" # Run command in gateway container
|
Tailscale:
1
2
3
4
5
| make tailscale-enable # Install Tailscale, verify, and lock down public SSH
make tailscale-setup # Install and register Tailscale only, no SSH lockdown
make tailscale-status # Check Tailscale connection status
make tailscale-ip # Get Tailscale IP
make tailscale-up # Manually authenticate Tailscale
|
Backup & Restore:
1
2
3
4
| make backup-now # Trigger backup immediately
make backup-pull # Download latest backup archive locally
make restore # List available backups (dry-run — safe)
make restore EXECUTE=1 BACKUP=<file> # Restore from backup
|
Optional add-ons (from Makefile.local):
1
2
| make addon-weather # Install morning weather cron
make patch-devices # Fix device pairing issues
|
Common Workflows#
Update OpenClaw#
Check for breaking changes first:
1
| gh release list --repo openclaw/openclaw --limit 10
|
Then:
1
2
3
4
| make backup-now # always backup before upgrading
# Edit docker/Dockerfile — bump OPENCLAW_VERSION
make deploy REBUILD=1
make logs
|
Update Configuration#
openclaw.json is gitignored — create it from openclaw.example.json if you haven’t already.
1
2
| vim openclaw.json
make deploy
|
Backup and Restore#
Backups run daily at 03:00 UTC via systemd timer.
1
2
3
4
5
| make backup-now # trigger immediately
make backup-pull # download latest archive locally
make restore # list available backups (safe, no changes)
make restore EXECUTE=1 BACKUP=openclaw_backup_20260101_030000.tar.gz
|
make restore without EXECUTE=1 is always safe. With EXECUTE=1 it stops containers, creates a safety backup of current state, extracts the archive, and restarts.
Access the Gateway#
The gateway binds to 127.0.0.1:18789 (localhost only).
Via SSH tunnel:
1
| make tunnel # localhost:18789 -> VPS:18789
|
Open http://localhost:18789 and paste your OPENCLAW_GATEWAY_TOKEN.
Via Tailscale Serve (if Tailscale is enabled):
1
2
3
| ssh -i $SSH_KEY openclaw@<tailscale-ip>
sudo tailscale serve --bg 18789
sudo tailscale serve status # prints your HTTPS URL
|
Dashboard available at https://openclaw-prod.<tailnet>.ts.net from any tailnet device.
Use Serve, not Funnel. Funnel exposes the service to the public internet.
Troubleshooting#
Terraform init fails — if using GCS backend, ensure authentication is configured. Run gcloud auth application-default login first. The default backend is local and requires no extra setup.
Container won’t start:
1
2
3
| make logs
make ssh
docker compose -f ~/openclaw/docker-compose.yml ps
|
Common causes: missing .env variables, invalid openclaw.json, API key issues. Fix with make deploy.
Can’t SSH to VPS — if ssh_allowed_cidrs='[]' (Tailscale-only mode), SSH via Tailscale IP or MagicDNS hostname. Emergency access via Hetzner web console.
SSH host key changed (after rebuilding the VPS):
1
| ssh-keygen -R <old_vps_ip>
|
Permission denied on ~/.openclaw — Docker took ownership via volume mount:
1
| ssh openclaw@VPS_IP "sudo chown -R openclaw:openclaw ~/.openclaw"
|
Dashboard shows “Pairing Required” — check trustedProxies is set in openclaw.json.
API billing error — verify key in ~/openclaw/.env on the VPS, or re-run make setup-auth for subscription auth.
CI/CD The built-in CI validates and tests Terraform (terraform fmt, terraform validate, tflint, terraform test). Ansible is never run in CI — all make bootstrap / make deploy operations are local only.
Example workflows for terraform plan (on PRs) and terraform apply (manual dispatch) are included as .example.yml files — copy them to enable (see Required Secrets below).
An optional GitOps deployment workflow is also included — copy .github/workflows/deploy.yml.example to .github/workflows/deploy.yml to enable automatic Ansible-based deployments on push. See GitOps auto-deploy for setup instructions.
...
GitOps Auto-Deploy A GitHub Actions pipeline can automatically deploy to the VPS on every push to main. This is optional — if you don’t configure the secrets below, the workflow won’t run and you can continue deploying manually with make deploy.
What It Does Triggered by changes to openclaw.json, docker/, docker-compose.yml, ansible/, or secrets/.env.enc Connects to the VPS via an ephemeral Tailscale node (no public SSH required) Decrypts secrets/.env.enc using SOPS + age key from GitHub Secrets If docker/ or docker-compose.yml changed: runs make deploy REBUILD=1 Otherwise: runs make deploy A manual rollback workflow is also included (.github/workflows/rollback.yml) — trigger it from the GitHub Actions UI with any previous git SHA. It also decrypts secrets with SOPS before deploying.
...
Remote State Backend By default Terraform uses a local backend (terraform.tfstate on disk). This requires zero setup and works fine for solo use. For team workflows or CI/CD, a remote backend gives you shared state, versioning, and locking.
Choosing a Backend terraform/envs/prod/backend.tf (copied from backend.tf.example) controls the backend:
1 2 3 4 5 6 7 8 9 10 11 12 # Option A — Local (default, no setup required) terraform { backend "local" {} } # Option B — GCS (recommended for teams / CI) terraform { backend "gcs" { bucket = "your-project-tfstate" prefix = "openclaw/prod" } } Terraform supports other backends too (S3, Azure Blob, Terraform Cloud). Any Terraform-compatible backend works — update backend.tf accordingly. Note that S3 requires a separate DynamoDB table for state locking, whereas GCS locks natively.
...