Terraform modules for deploying OpenClaw on Hetzner Cloud. Includes VPS provisioning, firewall configuration, cloud-init automation, and deployment tooling.
This repository provides infrastructure-as-code for deploying OpenClaw—an open-source AI coding assistant—on a Hetzner Cloud VPS. The setup includes:
For information about OpenClaw itself, see the OpenClaw documentation.
~/.ssh/id_rsa.pub or custom path (see SSH Configuration)git clone https://github.com/andreesg/openclaw-terraform-hetzner.git
cd openclaw-terraform-hetzner
cp config/inputs.example.sh config/inputs.sh
vim config/inputs.sh # Add your Hetzner API token and configuration
Required variables in config/inputs.sh:
HCLOUD_TOKEN - Hetzner Cloud API tokenTF_VAR_ssh_key_fingerprint - SSH key fingerprint from HetznerCONFIG_DIR - Path to your openclaw-docker-config repositorySERVER_IP - Address that scripts use to SSH into the VPS. Set to openclaw-prod when using Tailscale (MagicDNS hostname, stable across rebuilds). Leave unset to auto-detect from Terraform output (only works when public SSH is open).Tailscale (optional, recommended): Set
TF_VAR_enable_tailscale=trueandTF_VAR_tailscale_auth_keyto install Tailscale automatically on first boot — it lets you remove SSH from the public internet entirely. See Firewall Rules.
source config/inputs.sh
make init
make plan
make apply
make bootstrap
make deploy
make status
make logs
Access the gateway via SSH tunnel:
make tunnel # Opens tunnel on localhost:18789
If you enabled Tailscale, confirm it connected before closing public SSH access:
make tailscale-status # node should appear as connected
make tailscale-ip # note your Tailscale IP (e.g. 100.x.x.x)
┌─────────────────┐
│ Your Laptop │
│ │
│ ┌───────────┐ │ ┌─────────────────────┐
│ │ Terraform │──┼────────>│ Hetzner Cloud VPS │
│ └───────────┘ │ │ │
│ │ │ ┌──────────────┐ │
│ ┌───────────┐ │ │ │ Docker │ │
│ │ Config │──┼────────>│ │ OpenClaw │ │
│ │ Repo │ │ │ └──────────────┘ │
│ └───────────┘ │ │ │
└─────────────────┘ │ Firewall: SSH only │
└─────────────────────┘
│
v
┌─────────────────────┐
│ Hetzner Object │
│ Storage (state) │
└─────────────────────┘
| Component | Purpose | Location |
|---|---|---|
| infra/terraform/ | Infrastructure definitions | This repo |
| deploy/ | Deployment automation | This repo |
| docker/ | Container configuration | openclaw-docker-config |
| config/ | OpenClaw configuration | openclaw-docker-config |
Infrastructure:
make init # Initialize Terraform
make plan # Show infrastructure changes
make apply # Apply infrastructure changes
make destroy # Destroy all infrastructure
make output # Show Terraform outputs
Deployment:
make bootstrap # Initial OpenClaw setup
make deploy # Pull latest image and restart
make status # Check deployment status
make logs # Stream container logs
Operations:
make ssh # SSH to VPS as openclaw user
make tunnel # Create SSH tunnel to gateway
make backup-now # Trigger backup immediately
make restore # Restore from backup (BACKUP=filename)
Tailscale:
make tailscale-status # Check Tailscale status (uses public IP — run before closing port 22)
make tailscale-ip # Get Tailscale IP (uses public IP — run before closing port 22)
make tailscale-up # Manually authenticate Tailscale
Configuration:
make push-env # Push environment variables
make push-config # Push OpenClaw configuration
make setup-auth # Configure Claude subscription auth
By default, scripts use your SSH agent or the default key names (id_rsa, id_ed25519). If your key has a different name, set SSH_KEY in config/inputs.sh:
export SSH_KEY="$HOME/.ssh/your_key_name"
Default: CX23 (2 vCPU, 4GB RAM)
To change server type, add to config/inputs.sh:
export TF_VAR_server_type="cx32" # 4 vCPU, 8GB RAM
See Hetzner server types.
By default SSH (port 22) is open to 0.0.0.0/0. Restrict this before going to production.
To expose additional public TCP services through the Hetzner Cloud Firewall, set TF_VAR_additional_tcp_ports:
# In config/inputs.sh
export TF_VAR_additional_tcp_ports='[80,443]'
These ports are opened in both the Hetzner Cloud Firewall and the server's UFW firewall. They are exposed to 0.0.0.0/0 and ::/0. Port 22 remains managed separately by TF_VAR_ssh_allowed_cidrs.
Option A — Restrict to your IP:
# In config/inputs.sh
export TF_VAR_ssh_allowed_cidrs='["203.0.113.50/32"]'
Then apply:
source config/inputs.sh && make plan && make apply
Option B — Tailscale VPN (recommended):
Tailscale creates a private WireGuard mesh so SSH is reachable only from devices on your tailnet — the public IP has no open SSH port.
Get an auth key at login.tailscale.com/admin/settings/keys — use reusable + pre-authorized keys, not ephemeral.
Auth key expiry: Reusable Tailscale auth keys expire after 90 days by default. Generate a fresh key at login.tailscale.com/admin/settings/keys and update
TF_VAR_tailscale_auth_keyinconfig/inputs.sh.
Add to config/inputs.sh:
export TF_VAR_enable_tailscale=true
export TF_VAR_tailscale_auth_key="tskey-auth-xxxxxxxxxxxxx"
Deploy. Tailscale is installed automatically on first boot. Then verify it's working before closing public access:
source config/inputs.sh && make plan && make apply
make tailscale-status # confirm node is connected
make tailscale-ip # note your Tailscale IP
ssh openclaw@<tailscale-ip> # confirm Tailscale SSH works
Remove public SSH and point scripts at the Tailscale hostname:
# In config/inputs.sh
export TF_VAR_ssh_allowed_cidrs='[]'
export SERVER_IP="openclaw-prod" # Tailscale MagicDNS — stable across rebuilds
source config/inputs.sh && make plan && make apply
Make sure to always source
config/inputs.shbefore runningmakecommands so the updatedSERVER_IPis used.
Update openclaw.json in your openclaw-docker-config repo to enable Tailscale-based gateway auth:
{
"gateway": {
"auth": {
"allowTailscale": true
},
"controlUi": {
"allowInsecureAuth": true
}
}
}
Then push and restart:
make push-config deploy
allowTailscaleauthenticates dashboard users via Tailscale identity headers.
allowInsecureAuthlets the control UI authenticate over plain HTTP — safe because it's only availale in your private tailnet.
After step 5, all make commands (make ssh, make deploy, make status, etc.) connect via openclaw-prod on your tailnet — no IP to track down.
Recovery: If Tailscale fails to connect, check status with
make tailscale-status. For persistent issues, you can delete the Hetzner Cloud Firewall via the console or re-runmake applyafter fixing the configuration.
The S3 backend configuration is commented out by default in infra/terraform/envs/prod/main.tf. To enable:
config/inputs.sh:
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
main.tf and update endpoint URLterraform init -migrate-stateOpenClaw supports multiple AI providers. This setup defaults to Anthropic Claude, but you can switch to other providers by modifying the configuration in openclaw-docker-config.
Supported providers:
To switch providers:
Update openclaw.json in the config repo:
{
"agents": {
"defaults": {
"model": {
"primary": "openai/gpt-4"
}
}
},
"auth": {
"profiles": {
"openai:main": {
"provider": "openai",
"mode": "token"
}
}
}
}
Update secrets/openclaw.env:
OPENAI_API_KEY=sk-...
Redeploy:
make push-config deploy
See OpenClaw provider documentation for detailed configuration.
# 1. Configure secrets
cp config/inputs.example.sh config/inputs.sh
vim config/inputs.sh
# 2. Deploy infrastructure
source config/inputs.sh
make init plan apply
# 3. Bootstrap application
make bootstrap
# 4. Deploy OpenClaw
make deploy
# 5. Verify
make status logs
# Pull latest image and restart
make deploy
# Check logs
make logs
# Edit openclaw.json in config repo
vim ~/path/to/openclaw-docker-config/config/openclaw.json
# Push and restart
make push-config deploy
Backups run daily at 02:00 UTC via systemd timer.
# Manual backup
make backup-now
# List backups
make ssh
ls -lh ~/backups/
# Restore from backup
make restore BACKUP=openclaw-backup-2026-02-08.tar.gz
OpenClaw gateway runs on 127.0.0.1:18789 (localhost only) for security.
Access via SSH tunnel:
make tunnel # Creates tunnel: localhost:18789 -> VPS:18789
Then open http://localhost:18789 in your browser. The gateway will ask for your Gateway Token — paste your OPENCLAW_GATEWAY_TOKEN value (from secrets/openclaw.env) into the settings field to authenticate.
Access via Tailscale Serve (if Tailscale is enabled):
ssh openclaw@<tailscale-ip>
sudo tailscale serve --bg 18789
sudo tailscale serve status # prints your HTTPS URL
Dashboard is then available at https://openclaw-prod.<tailnet>.ts.net from any device on your tailnet — no tunnel needed.
Note: Use Serve, not Funnel. Funnel makes the service publicly accessible on the internet. See OpenClaw Tailscale gateway docs for full configuration options including the
allowTailscalesetting.
Cause: S3 backend credentials not set
Solution:
source config/inputs.sh
make init
Or use local state by commenting out the backend block in infra/terraform/envs/prod/main.tf.
Check logs:
make logs
make ssh
docker compose -f ~/openclaw/docker-compose.yml ps
Common causes:
.envFix:
make push-env # Re-push environment variables
make push-config # Re-push OpenClaw config
make deploy # Restart
Check firewall rules:
grep TF_VAR_ssh_allowed_cidrs config/inputs.sh
# Check actual firewall
make ssh-root
ufw status
If ssh_allowed_cidrs='[]' (Tailscale-only mode), make ssh connects via the public IP and will time out, that's expected. SSH via your Tailscale IP instead:
ssh openclaw@<tailscale-ip>
Or - as stated above - use the SERVER_IP variable to point make ssh at the Tailscale hostname:
# In config/inputs.sh
export TF_VAR_ssh_allowed_cidrs='[]'
export SERVER_IP="openclaw-prod" # Tailscale MagicDNS — stable across rebuilds
source config/inputs.sh && make plan && make apply
Emergency access: Hetzner web console → server → Console.
If you see Permission denied when creating directories under ~/.openclaw (e.g. during make setup-auth), Docker likely took ownership of the directory via the volume mount. This can happen if you ran make deploy before bootstrap finished, or if you're re-running bootstrap after a previous deploy.
Fix:
ssh openclaw@VPS_IP "sudo chown -R openclaw:openclaw ~/.openclaw"
Then re-run make bootstrap or make setup-auth.
Verify prerequisites:
# Check CONFIG_DIR is set and exists
echo $CONFIG_DIR
ls $CONFIG_DIR/docker/docker-compose.yml
# Verify GHCR credentials
docker login ghcr.io -u YOUR_GITHUB_USERNAME
Cause: Destroyed and re-provisioned the VPS — new server has a different host key at the same public IP.
Error: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
Fix:
ssh-keygen -R <old_vps_ip>
# Then retry — SSH will prompt you to accept the new key.
Anthropic API key issues:
If using API key (not subscription):
# Check key is set
make ssh
grep ANTHROPIC_API_KEY ~/openclaw/.env
# Verify key has credits at console.anthropic.com
If using Claude subscription:
# Re-run setup-auth
make setup-auth
# Verify auth profile exists
make ssh
cat ~/.openclaw/agents/main/agent/auth-profiles.json
See SECURITY.md for the full security policy and threat model.
0.0.0.0/0) — restrict before productionTF_VAR_ssh_allowed_cidrsssh_allowed_cidrs='[]' — zero public SSH exposureconfig/inputs.sh or secrets/openclaw.env.gitignore before committing127.0.0.1 (localhost only) — never exposed directlyTF_VAR_additional_tcp_ports opens ports in both the Hetzner Cloud Firewall and UFW on the VPSTF_VAR_additional_tcp_ports only for services that should be publicly reachableinfra/terraform/modules/hetzner-vps/main.tf for the full firewall rule set.
├── infra/
│ ├── terraform/
│ │ ├── globals/ # Shared configuration
│ │ ├── envs/prod/ # Production environment
│ │ └── modules/ # Reusable modules
│ │ └── hetzner-vps/ # VPS module
│ └── cloud-init/
│ └── user-data.yml.tpl # Server initialization
├── deploy/ # Deployment scripts
│ ├── bootstrap.sh # Initial setup
│ ├── deploy.sh # Deploy/update
│ ├── backup.sh # Backup script
│ └── restore.sh # Restore script
├── scripts/ # Utility scripts
│ ├── push-env.sh # Push secrets to VPS
│ ├── push-config.sh # Push config to VPS
│ └── setup-auth.sh # Setup subscription auth
├── config/
│ └── inputs.example.sh # Configuration template
└── secrets/
└── openclaw.env.example # Secrets template
See Hetzner Cloud pricing for current rates. This setup uses a small shared VPS (default: CX23) plus minimal object storage for Terraform state.
Note: Prices exclude Anthropic/OpenAI API costs.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Ways to contribute:
This project is licensed under the MIT License - see the LICENSE file for details.
Shell
62.0%
HCL
20.5%
Makefile
11.7%
Smarty
5.9%
Terraform modules for deploying OpenClaw on Hetzner Cloud. Includes VPS provisioning, firewall configuration, cloud-init automation, and deployment tooling.
This repository provides infrastructure-as-code for deploying OpenClaw—an open-source AI coding assistant—on a Hetzner Cloud VPS. The setup includes:
For information about OpenClaw itself, see the OpenClaw documentation.
~/.ssh/id_rsa.pub or custom path (see SSH Configuration)git clone https://github.com/andreesg/openclaw-terraform-hetzner.git
cd openclaw-terraform-hetzner
cp config/inputs.example.sh config/inputs.sh
vim config/inputs.sh # Add your Hetzner API token and configuration
Required variables in config/inputs.sh:
HCLOUD_TOKEN - Hetzner Cloud API tokenTF_VAR_ssh_key_fingerprint - SSH key fingerprint from HetznerCONFIG_DIR - Path to your openclaw-docker-config repositorySERVER_IP - Address that scripts use to SSH into the VPS. Set to openclaw-prod when using Tailscale (MagicDNS hostname, stable across rebuilds). Leave unset to auto-detect from Terraform output (only works when public SSH is open).Tailscale (optional, recommended): Set
TF_VAR_enable_tailscale=trueandTF_VAR_tailscale_auth_keyto install Tailscale automatically on first boot — it lets you remove SSH from the public internet entirely. See Firewall Rules.
source config/inputs.sh
make init
make plan
make apply
make bootstrap
make deploy
make status
make logs
Access the gateway via SSH tunnel:
make tunnel # Opens tunnel on localhost:18789
If you enabled Tailscale, confirm it connected before closing public SSH access:
make tailscale-status # node should appear as connected
make tailscale-ip # note your Tailscale IP (e.g. 100.x.x.x)
┌─────────────────┐
│ Your Laptop │
│ │
│ ┌───────────┐ │ ┌─────────────────────┐
│ │ Terraform │──┼────────>│ Hetzner Cloud VPS │
│ └───────────┘ │ │ │
│ │ │ ┌──────────────┐ │
│ ┌───────────┐ │ │ │ Docker │ │
│ │ Config │──┼────────>│ │ OpenClaw │ │
│ │ Repo │ │ │ └──────────────┘ │
│ └───────────┘ │ │ │
└─────────────────┘ │ Firewall: SSH only │
└─────────────────────┘
│
v
┌─────────────────────┐
│ Hetzner Object │
│ Storage (state) │
└─────────────────────┘
| Component | Purpose | Location |
|---|---|---|
| infra/terraform/ | Infrastructure definitions | This repo |
| deploy/ | Deployment automation | This repo |
| docker/ | Container configuration | openclaw-docker-config |
| config/ | OpenClaw configuration | openclaw-docker-config |
Infrastructure:
make init # Initialize Terraform
make plan # Show infrastructure changes
make apply # Apply infrastructure changes
make destroy # Destroy all infrastructure
make output # Show Terraform outputs
Deployment:
make bootstrap # Initial OpenClaw setup
make deploy # Pull latest image and restart
make status # Check deployment status
make logs # Stream container logs
Operations:
make ssh # SSH to VPS as openclaw user
make tunnel # Create SSH tunnel to gateway
make backup-now # Trigger backup immediately
make restore # Restore from backup (BACKUP=filename)
Tailscale:
make tailscale-status # Check Tailscale status (uses public IP — run before closing port 22)
make tailscale-ip # Get Tailscale IP (uses public IP — run before closing port 22)
make tailscale-up # Manually authenticate Tailscale
Configuration:
make push-env # Push environment variables
make push-config # Push OpenClaw configuration
make setup-auth # Configure Claude subscription auth
By default, scripts use your SSH agent or the default key names (id_rsa, id_ed25519). If your key has a different name, set SSH_KEY in config/inputs.sh:
export SSH_KEY="$HOME/.ssh/your_key_name"
Default: CX23 (2 vCPU, 4GB RAM)
To change server type, add to config/inputs.sh:
export TF_VAR_server_type="cx32" # 4 vCPU, 8GB RAM
See Hetzner server types.
By default SSH (port 22) is open to 0.0.0.0/0. Restrict this before going to production.
To expose additional public TCP services through the Hetzner Cloud Firewall, set TF_VAR_additional_tcp_ports:
# In config/inputs.sh
export TF_VAR_additional_tcp_ports='[80,443]'
These ports are opened in both the Hetzner Cloud Firewall and the server's UFW firewall. They are exposed to 0.0.0.0/0 and ::/0. Port 22 remains managed separately by TF_VAR_ssh_allowed_cidrs.
Option A — Restrict to your IP:
# In config/inputs.sh
export TF_VAR_ssh_allowed_cidrs='["203.0.113.50/32"]'
Then apply:
source config/inputs.sh && make plan && make apply
Option B — Tailscale VPN (recommended):
Tailscale creates a private WireGuard mesh so SSH is reachable only from devices on your tailnet — the public IP has no open SSH port.
Get an auth key at login.tailscale.com/admin/settings/keys — use reusable + pre-authorized keys, not ephemeral.
Auth key expiry: Reusable Tailscale auth keys expire after 90 days by default. Generate a fresh key at login.tailscale.com/admin/settings/keys and update
TF_VAR_tailscale_auth_keyinconfig/inputs.sh.
Add to config/inputs.sh:
export TF_VAR_enable_tailscale=true
export TF_VAR_tailscale_auth_key="tskey-auth-xxxxxxxxxxxxx"
Deploy. Tailscale is installed automatically on first boot. Then verify it's working before closing public access:
source config/inputs.sh && make plan && make apply
make tailscale-status # confirm node is connected
make tailscale-ip # note your Tailscale IP
ssh openclaw@<tailscale-ip> # confirm Tailscale SSH works
Remove public SSH and point scripts at the Tailscale hostname:
# In config/inputs.sh
export TF_VAR_ssh_allowed_cidrs='[]'
export SERVER_IP="openclaw-prod" # Tailscale MagicDNS — stable across rebuilds
source config/inputs.sh && make plan && make apply
Make sure to always source
config/inputs.shbefore runningmakecommands so the updatedSERVER_IPis used.
Update openclaw.json in your openclaw-docker-config repo to enable Tailscale-based gateway auth:
{
"gateway": {
"auth": {
"allowTailscale": true
},
"controlUi": {
"allowInsecureAuth": true
}
}
}
Then push and restart:
make push-config deploy
allowTailscaleauthenticates dashboard users via Tailscale identity headers.
allowInsecureAuthlets the control UI authenticate over plain HTTP — safe because it's only availale in your private tailnet.
After step 5, all make commands (make ssh, make deploy, make status, etc.) connect via openclaw-prod on your tailnet — no IP to track down.
Recovery: If Tailscale fails to connect, check status with
make tailscale-status. For persistent issues, you can delete the Hetzner Cloud Firewall via the console or re-runmake applyafter fixing the configuration.
The S3 backend configuration is commented out by default in infra/terraform/envs/prod/main.tf. To enable:
config/inputs.sh:
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
main.tf and update endpoint URLterraform init -migrate-stateOpenClaw supports multiple AI providers. This setup defaults to Anthropic Claude, but you can switch to other providers by modifying the configuration in openclaw-docker-config.
Supported providers:
To switch providers:
Update openclaw.json in the config repo:
{
"agents": {
"defaults": {
"model": {
"primary": "openai/gpt-4"
}
}
},
"auth": {
"profiles": {
"openai:main": {
"provider": "openai",
"mode": "token"
}
}
}
}
Update secrets/openclaw.env:
OPENAI_API_KEY=sk-...
Redeploy:
make push-config deploy
See OpenClaw provider documentation for detailed configuration.
# 1. Configure secrets
cp config/inputs.example.sh config/inputs.sh
vim config/inputs.sh
# 2. Deploy infrastructure
source config/inputs.sh
make init plan apply
# 3. Bootstrap application
make bootstrap
# 4. Deploy OpenClaw
make deploy
# 5. Verify
make status logs
# Pull latest image and restart
make deploy
# Check logs
make logs
# Edit openclaw.json in config repo
vim ~/path/to/openclaw-docker-config/config/openclaw.json
# Push and restart
make push-config deploy
Backups run daily at 02:00 UTC via systemd timer.
# Manual backup
make backup-now
# List backups
make ssh
ls -lh ~/backups/
# Restore from backup
make restore BACKUP=openclaw-backup-2026-02-08.tar.gz
OpenClaw gateway runs on 127.0.0.1:18789 (localhost only) for security.
Access via SSH tunnel:
make tunnel # Creates tunnel: localhost:18789 -> VPS:18789
Then open http://localhost:18789 in your browser. The gateway will ask for your Gateway Token — paste your OPENCLAW_GATEWAY_TOKEN value (from secrets/openclaw.env) into the settings field to authenticate.
Access via Tailscale Serve (if Tailscale is enabled):
ssh openclaw@<tailscale-ip>
sudo tailscale serve --bg 18789
sudo tailscale serve status # prints your HTTPS URL
Dashboard is then available at https://openclaw-prod.<tailnet>.ts.net from any device on your tailnet — no tunnel needed.
Note: Use Serve, not Funnel. Funnel makes the service publicly accessible on the internet. See OpenClaw Tailscale gateway docs for full configuration options including the
allowTailscalesetting.
Cause: S3 backend credentials not set
Solution:
source config/inputs.sh
make init
Or use local state by commenting out the backend block in infra/terraform/envs/prod/main.tf.
Check logs:
make logs
make ssh
docker compose -f ~/openclaw/docker-compose.yml ps
Common causes:
.envFix:
make push-env # Re-push environment variables
make push-config # Re-push OpenClaw config
make deploy # Restart
Check firewall rules:
grep TF_VAR_ssh_allowed_cidrs config/inputs.sh
# Check actual firewall
make ssh-root
ufw status
If ssh_allowed_cidrs='[]' (Tailscale-only mode), make ssh connects via the public IP and will time out, that's expected. SSH via your Tailscale IP instead:
ssh openclaw@<tailscale-ip>
Or - as stated above - use the SERVER_IP variable to point make ssh at the Tailscale hostname:
# In config/inputs.sh
export TF_VAR_ssh_allowed_cidrs='[]'
export SERVER_IP="openclaw-prod" # Tailscale MagicDNS — stable across rebuilds
source config/inputs.sh && make plan && make apply
Emergency access: Hetzner web console → server → Console.
If you see Permission denied when creating directories under ~/.openclaw (e.g. during make setup-auth), Docker likely took ownership of the directory via the volume mount. This can happen if you ran make deploy before bootstrap finished, or if you're re-running bootstrap after a previous deploy.
Fix:
ssh openclaw@VPS_IP "sudo chown -R openclaw:openclaw ~/.openclaw"
Then re-run make bootstrap or make setup-auth.
Verify prerequisites:
# Check CONFIG_DIR is set and exists
echo $CONFIG_DIR
ls $CONFIG_DIR/docker/docker-compose.yml
# Verify GHCR credentials
docker login ghcr.io -u YOUR_GITHUB_USERNAME
Cause: Destroyed and re-provisioned the VPS — new server has a different host key at the same public IP.
Error: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
Fix:
ssh-keygen -R <old_vps_ip>
# Then retry — SSH will prompt you to accept the new key.
Anthropic API key issues:
If using API key (not subscription):
# Check key is set
make ssh
grep ANTHROPIC_API_KEY ~/openclaw/.env
# Verify key has credits at console.anthropic.com
If using Claude subscription:
# Re-run setup-auth
make setup-auth
# Verify auth profile exists
make ssh
cat ~/.openclaw/agents/main/agent/auth-profiles.json
See SECURITY.md for the full security policy and threat model.
0.0.0.0/0) — restrict before productionTF_VAR_ssh_allowed_cidrsssh_allowed_cidrs='[]' — zero public SSH exposureconfig/inputs.sh or secrets/openclaw.env.gitignore before committing127.0.0.1 (localhost only) — never exposed directlyTF_VAR_additional_tcp_ports opens ports in both the Hetzner Cloud Firewall and UFW on the VPSTF_VAR_additional_tcp_ports only for services that should be publicly reachableinfra/terraform/modules/hetzner-vps/main.tf for the full firewall rule set.
├── infra/
│ ├── terraform/
│ │ ├── globals/ # Shared configuration
│ │ ├── envs/prod/ # Production environment
│ │ └── modules/ # Reusable modules
│ │ └── hetzner-vps/ # VPS module
│ └── cloud-init/
│ └── user-data.yml.tpl # Server initialization
├── deploy/ # Deployment scripts
│ ├── bootstrap.sh # Initial setup
│ ├── deploy.sh # Deploy/update
│ ├── backup.sh # Backup script
│ └── restore.sh # Restore script
├── scripts/ # Utility scripts
│ ├── push-env.sh # Push secrets to VPS
│ ├── push-config.sh # Push config to VPS
│ └── setup-auth.sh # Setup subscription auth
├── config/
│ └── inputs.example.sh # Configuration template
└── secrets/
└── openclaw.env.example # Secrets template
See Hetzner Cloud pricing for current rates. This setup uses a small shared VPS (default: CX23) plus minimal object storage for Terraform state.
Note: Prices exclude Anthropic/OpenAI API costs.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Ways to contribute:
This project is licensed under the MIT License - see the LICENSE file for details.
Shell
62.0%
HCL
20.5%
Makefile
11.7%
Smarty
5.9%