Files
openclaw/docs/install/hetzner.md

357 lines
9.9 KiB
Markdown
Raw Normal View History

2026-01-08 23:06:52 -08:00
---
2026-01-31 21:13:13 +09:00
summary: "Run OpenClaw Gateway 24/7 on a cheap Hetzner VPS (Docker) with durable state and baked-in binaries"
2026-01-08 23:06:52 -08:00
read_when:
2026-01-30 03:15:10 +01:00
- You want OpenClaw running 24/7 on a cloud VPS (not your laptop)
2026-01-08 23:06:52 -08:00
- You want a production-grade, always-on Gateway on your own VPS
- You want full control over persistence, binaries, and restart behavior
2026-01-30 03:15:10 +01:00
- You are running OpenClaw in Docker on Hetzner or a similar provider
title: "Hetzner"
2026-01-08 23:06:52 -08:00
---
2026-01-30 03:15:10 +01:00
# OpenClaw on Hetzner (Docker, Production VPS Guide)
2026-01-08 23:06:52 -08:00
## Goal
2026-01-30 03:15:10 +01:00
Run a persistent OpenClaw Gateway on a Hetzner VPS using Docker, with durable state, baked-in binaries, and safe restart behavior.
2026-01-08 23:06:52 -08:00
2026-01-30 03:15:10 +01:00
If you want “OpenClaw 24/7 for ~$5”, this is the simplest reliable setup.
2026-01-10 01:43:28 +01:00
Hetzner pricing changes; pick the smallest Debian/Ubuntu VPS and scale up if you hit OOMs.
Security model reminder:
- Company-shared agents are fine when everyone is in the same trust boundary and the runtime is business-only.
- Keep strict separation: dedicated VPS/runtime + dedicated accounts; no personal Apple/Google/browser/password-manager profiles on that host.
- If users are adversarial to each other, split by gateway/host/OS user.
See [Security](/gateway/security) and [VPS hosting](/vps).
2026-01-10 01:43:28 +01:00
## What are we doing (simple terms)?
- Rent a small Linux server (Hetzner VPS)
- Install Docker (isolated app runtime)
2026-01-30 03:15:10 +01:00
- Start the OpenClaw Gateway in Docker
- Persist `~/.openclaw` + `~/.openclaw/workspace` on the host (survives restarts/rebuilds)
2026-01-10 01:43:28 +01:00
- Access the Control UI from your laptop via an SSH tunnel
2026-01-08 23:06:52 -08:00
The Gateway can be accessed via:
2026-01-08 23:06:52 -08:00
- SSH port forwarding from your laptop
- Direct port exposure if you manage firewalling and tokens yourself
This guide assumes Ubuntu or Debian on Hetzner.
If you are on another Linux VPS, map packages accordingly.
For the generic Docker flow, see [Docker](/install/docker).
2026-01-08 23:06:52 -08:00
---
## Quick path (experienced operators)
1. Provision Hetzner VPS
2. Install Docker
3. Clone OpenClaw repository
4. Create persistent host directories
5. Configure `.env` and `docker-compose.yml`
6. Bake required binaries into the image
7. `docker compose up -d`
8. Verify persistence and Gateway access
2026-01-08 23:06:52 -08:00
---
## What you need
- Hetzner VPS with root access
- SSH access from your laptop
- Basic comfort with SSH + copy/paste
- ~20 minutes
- Docker and Docker Compose
- Model auth credentials
- Optional provider credentials
- WhatsApp QR
- Telegram bot token
- Gmail OAuth
2026-01-08 23:06:52 -08:00
---
## 1) Provision the VPS
Create an Ubuntu or Debian VPS in Hetzner.
Connect as root:
```bash
ssh root@YOUR_VPS_IP
```
This guide assumes the VPS is stateful.
Do not treat it as disposable infrastructure.
---
## 2) Install Docker (on the VPS)
```bash
apt-get update
apt-get install -y git curl ca-certificates
curl -fsSL https://get.docker.com | sh
```
Verify:
```bash
docker --version
docker compose version
```
---
2026-01-30 03:15:10 +01:00
## 3) Clone the OpenClaw repository
2026-01-08 23:06:52 -08:00
```bash
2026-01-30 03:15:10 +01:00
git clone https://github.com/openclaw/openclaw.git
cd openclaw
2026-01-08 23:06:52 -08:00
```
This guide assumes you will build a custom image to guarantee binary persistence.
---
## 4) Create persistent host directories
Docker containers are ephemeral.
All long-lived state must live on the host.
```bash
2026-01-30 03:15:10 +01:00
mkdir -p /root/.openclaw/workspace
2026-01-08 23:06:52 -08:00
# Set ownership to the container user (uid 1000):
2026-01-30 03:15:10 +01:00
chown -R 1000:1000 /root/.openclaw
2026-01-08 23:06:52 -08:00
```
---
## 5) Configure environment variables
Create `.env` in the repository root.
```bash
2026-01-30 03:15:10 +01:00
OPENCLAW_IMAGE=openclaw:latest
OPENCLAW_GATEWAY_TOKEN=change-me-now
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_GATEWAY_PORT=18789
2026-01-08 23:06:52 -08:00
2026-01-30 03:15:10 +01:00
OPENCLAW_CONFIG_DIR=/root/.openclaw
OPENCLAW_WORKSPACE_DIR=/root/.openclaw/workspace
2026-01-08 23:06:52 -08:00
GOG_KEYRING_PASSWORD=change-me-now
2026-01-30 03:15:10 +01:00
XDG_CONFIG_HOME=/home/node/.openclaw
2026-01-08 23:06:52 -08:00
```
2026-01-10 01:43:28 +01:00
Generate strong secrets:
```bash
openssl rand -hex 32
```
2026-01-08 23:06:52 -08:00
**Do not commit this file.**
---
## 6) Docker Compose configuration
Create or update `docker-compose.yml`.
```yaml
services:
2026-01-30 03:15:10 +01:00
openclaw-gateway:
image: ${OPENCLAW_IMAGE}
build: .
2026-01-08 23:06:52 -08:00
restart: unless-stopped
env_file:
- .env
environment:
- HOME=/home/node
2026-01-08 23:06:52 -08:00
- NODE_ENV=production
- TERM=xterm-256color
2026-01-30 03:15:10 +01:00
- OPENCLAW_GATEWAY_BIND=${OPENCLAW_GATEWAY_BIND}
- OPENCLAW_GATEWAY_PORT=${OPENCLAW_GATEWAY_PORT}
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
2026-01-08 23:06:52 -08:00
- GOG_KEYRING_PASSWORD=${GOG_KEYRING_PASSWORD}
- XDG_CONFIG_HOME=${XDG_CONFIG_HOME}
- PATH=/home/linuxbrew/.linuxbrew/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
volumes:
2026-01-30 03:15:10 +01:00
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
2026-01-08 23:06:52 -08:00
ports:
2026-01-10 01:43:28 +01:00
# Recommended: keep the Gateway loopback-only on the VPS; access via SSH tunnel.
# To expose it publicly, remove the `127.0.0.1:` prefix and firewall accordingly.
2026-01-31 21:13:13 +09:00
- "127.0.0.1:${OPENCLAW_GATEWAY_PORT}:18789"
command:
[
2026-01-31 21:13:13 +09:00
"node",
"dist/index.js",
"gateway",
"--bind",
"${OPENCLAW_GATEWAY_BIND}",
"--port",
"${OPENCLAW_GATEWAY_PORT}",
"--allow-unconfigured",
]
2026-01-08 23:06:52 -08:00
```
`--allow-unconfigured` is only for bootstrap convenience, it is not a replacement for a proper gateway configuration. Still set auth (`gateway.auth.token` or password) and use safe bind settings for your deployment.
2026-01-08 23:06:52 -08:00
---
## 7) Bake required binaries into the image (critical)
Installing binaries inside a running container is a trap.
Anything installed at runtime will be lost on restart.
All external binaries required by skills must be installed at image build time.
The examples below show three common binaries only:
2026-01-08 23:06:52 -08:00
- `gog` for Gmail access
- `goplaces` for Google Places
- `wacli` for WhatsApp
These are examples, not a complete list.
You may install as many binaries as needed using the same pattern.
If you add new skills later that depend on additional binaries, you must:
2026-01-08 23:06:52 -08:00
1. Update the Dockerfile
2. Rebuild the image
3. Restart the containers
**Example Dockerfile**
```dockerfile
FROM node:24-bookworm
2026-01-08 23:06:52 -08:00
RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/*
# Example binary 1: Gmail CLI
RUN curl -L https://github.com/steipete/gog/releases/latest/download/gog_Linux_x86_64.tar.gz \
| tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/gog
# Example binary 2: Google Places CLI
RUN curl -L https://github.com/steipete/goplaces/releases/latest/download/goplaces_Linux_x86_64.tar.gz \
| tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/goplaces
# Example binary 3: WhatsApp CLI
RUN curl -L https://github.com/steipete/wacli/releases/latest/download/wacli_Linux_x86_64.tar.gz \
| tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/wacli
# Add more binaries below using the same pattern
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY scripts ./scripts
2026-01-08 23:06:52 -08:00
RUN corepack enable
RUN pnpm install --frozen-lockfile
COPY . .
2026-01-08 23:06:52 -08:00
RUN pnpm build
RUN pnpm ui:install
RUN pnpm ui:build
2026-01-10 01:43:28 +01:00
ENV NODE_ENV=production
CMD ["node","dist/index.js"]
2026-01-08 23:06:52 -08:00
```
---
## 8) Build and launch
```bash
docker compose build
2026-01-30 03:15:10 +01:00
docker compose up -d openclaw-gateway
2026-01-08 23:06:52 -08:00
```
Verify binaries:
```bash
2026-01-30 03:15:10 +01:00
docker compose exec openclaw-gateway which gog
docker compose exec openclaw-gateway which goplaces
docker compose exec openclaw-gateway which wacli
2026-01-08 23:06:52 -08:00
```
Expected output:
```
/usr/local/bin/gog
/usr/local/bin/goplaces
/usr/local/bin/wacli
```
---
## 9) Verify Gateway
```bash
2026-01-30 03:15:10 +01:00
docker compose logs -f openclaw-gateway
2026-01-08 23:06:52 -08:00
```
Success:
```
[gateway] listening on ws://0.0.0.0:18789
```
From your laptop:
```bash
ssh -N -L 18789:127.0.0.1:18789 root@YOUR_VPS_IP
```
Open:
`http://127.0.0.1:18789/`
Paste your gateway token.
---
## What persists where (source of truth)
2026-01-30 03:15:10 +01:00
OpenClaw runs in Docker, but Docker is not the source of truth.
2026-01-08 23:06:52 -08:00
All long-lived state must survive restarts, rebuilds, and reboots.
| Component | Location | Persistence mechanism | Notes |
| ------------------- | --------------------------------- | ---------------------- | -------------------------------- |
| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens |
| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys |
| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state |
| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts |
| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login |
| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` |
| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time |
| Node runtime | Container filesystem | Docker image | Rebuilt every image build |
| OS packages | Container filesystem | Docker image | Do not install at runtime |
| Docker container | Ephemeral | Restartable | Safe to destroy |
---
## Infrastructure as Code (Terraform)
For teams preferring infrastructure-as-code workflows, a community-maintained Terraform setup provides:
- Modular Terraform configuration with remote state management
- Automated provisioning via cloud-init
- Deployment scripts (bootstrap, deploy, backup/restore)
- Security hardening (firewall, UFW, SSH-only access)
- SSH tunnel configuration for gateway access
**Repositories:**
Feat/litellm provider (#12823) * feat: add LiteLLM provider types, env var, credentials, and auth choice Add litellm-api-key auth choice, LITELLM_API_KEY env var mapping, setLitellmApiKey() credential storage, and LITELLM_DEFAULT_MODEL_REF. * feat: add LiteLLM onboarding handler and provider config Add applyLitellmProviderConfig which properly registers models.providers.litellm with baseUrl, api type, and model definitions. This fixes the critical bug from PR #6488 where the provider entry was never created, causing model resolution to fail at runtime. * docs: add LiteLLM provider documentation Add setup guide covering onboarding, manual config, virtual keys, model routing, and usage tracking. Link from provider index. * docs: add LiteLLM to sidebar navigation in docs.json Add providers/litellm to both English and Chinese provider page lists so the docs page appears in the sidebar navigation. * test: add LiteLLM non-interactive onboarding test Wire up litellmApiKey flag inference and auth-choice handler for the non-interactive onboarding path, and add an integration test covering profile, model default, and credential storage. * fix: register --litellm-api-key CLI flag and add preferred provider mapping Wire up the missing Commander CLI option, action handler mapping, and help text for --litellm-api-key. Add litellm-api-key to the preferred provider map for consistency with other providers. * fix: remove zh-CN sidebar entry for litellm (no localized page yet) * style: format buildLitellmModelDefinition return type * fix(onboarding): harden LiteLLM provider setup (#12823) * refactor(onboarding): keep auth-choice provider dispatcher under size limit --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-02-11 02:46:56 -08:00
- Infrastructure: [openclaw-terraform-hetzner](https://github.com/andreesg/openclaw-terraform-hetzner)
- Docker config: [openclaw-docker-config](https://github.com/andreesg/openclaw-docker-config)
This approach complements the Docker setup above with reproducible deployments, version-controlled infrastructure, and automated disaster recovery.
> **Note:** Community-maintained. For issues or contributions, see the repository links above.