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
2026-01-31 16:04:03 -05:00
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-31 18:31:49 +09:00
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.
2026-02-24 01:02:06 +00:00
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-31 18:31:49 +09:00
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.
2026-01-09 18:21:28 +01:00
For the generic Docker flow, see [Docker ](/install/docker ).
2026-01-08 23:06:52 -08:00
---
## Quick path (experienced operators)
2026-01-31 18:31:49 +09:00
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
2026-01-31 18:31:49 +09:00
- 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}
2026-01-09 18:17:19 +01:00
build: .
2026-01-08 23:06:52 -08:00
restart: unless-stopped
env_file:
- .env
environment:
2026-01-09 18:17:19 +01:00
- 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"
2026-01-09 18:17:19 +01:00
command:
[
2026-01-31 21:13:13 +09:00
"node",
"dist/index.js",
"gateway",
"--bind",
"${OPENCLAW_GATEWAY_BIND}",
"--port",
"${OPENCLAW_GATEWAY_PORT}",
2026-02-09 16:02:54 +01:00
"--allow-unconfigured",
2026-01-09 18:17:19 +01:00
]
2026-01-08 23:06:52 -08:00
```
2026-02-09 16:02:54 +01: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-31 18:31:49 +09:00
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-31 18:31:49 +09:00
2026-01-08 23:06:52 -08:00
1. Update the Dockerfile
2. Rebuild the image
3. Restart the containers
**Example Dockerfile**
```dockerfile
2026-03-12 15:09:23 +03:00
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
2026-01-09 15:23:06 -05:00
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
2026-01-09 15:23:06 -05:00
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
2026-01-31 18:31:49 +09:00
CMD ["node","dist/index.js"]
2026-01-08 23:06:52 -08:00
```
---
## 8) Build and launch
```bash
2026-01-09 18:17:19 +01:00
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.
2026-01-31 18:31:49 +09:00
| 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 |
2026-02-10 09:56:54 +00:00
---
## 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
2026-02-10 09:56:54 +00: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.