From 1c1e8c8d136e23f9d4c028a220f5819c1421956c Mon Sep 17 00:00:00 2001 From: Jack Spiering <46534141+jackspiering@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:24:06 +0200 Subject: [PATCH 1/2] OpenClaw: new service Adds the OpenClaw Gateway behind a Tailscale sidecar. The official image has no tailscale CLI, so OpenClaw's own Serve mode cannot run in the container. Closes #234. --- README.md | 1 + services/openclaw/.env | 30 +++++++++++++ services/openclaw/README.md | 51 +++++++++++++++++++++ services/openclaw/compose.yaml | 82 ++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 services/openclaw/.env create mode 100644 services/openclaw/README.md create mode 100644 services/openclaw/compose.yaml diff --git a/README.md b/README.md index 290b99f1..128fc588 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ Every stack starts from the same [standard setup](documentation/standard-setup.m | **Nanote** | Note-taking app that stores notes as Markdown files in folders. | [Details](services/nanote) | | **NextExplorer** | Web file manager for browsing, uploading, and editing files in one folder on your server. | [Details](services/next-explorer) | | **Open WebUI** | Chat interface for AI models, through Ollama or OpenAI-compatible APIs. | [Details](services/open-webui) | +| **OpenClaw** | Personal AI assistant that runs agents and connects them to your chat apps. | [Details](services/openclaw) | | **Paperless-ngx** | Document management system that turns scans and files into a searchable archive. | [Details](services/paperless) | | **Pingvin Share** | Archived. File sharing platform with links that can expire. | [Details](services/pingvin-share) | | **Radicale** | CalDAV and CardDAV server that syncs calendars, to-do lists, and contacts. | [Details](services/radicale) | diff --git a/services/openclaw/.env b/services/openclaw/.env new file mode 100644 index 00000000..57293043 --- /dev/null +++ b/services/openclaw/.env @@ -0,0 +1,30 @@ +#URL=https://github.com/tailscale-dev/ScaleTail +# To run a second copy, copy the folder under another name and change SERVICE. + +# Service Configuration +# Service name (e.g., adguard). Used as hostname in Tailscale and for container naming (app-${SERVICE}). +SERVICE=openclaw +# Docker image URL from container registry (e.g., adguard/adguardhome:latest). +IMAGE_URL=ghcr.io/openclaw/openclaw:latest + +# Network Configuration +# Port to expose to local network. Uncomment the "ports:" section in compose.yaml to enable. +SERVICEPORT=18789 +# Preferred DNS server for Tailscale. Uncomment the "dns:" section in compose.yaml to enable. +DNS_SERVER=9.9.9.9 + +# Tailscale Configuration +# Auth key from https://console.tailscale.com/admin/settings/keys. See: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key for instructions. +TS_AUTHKEY= + +# Time Zone setting for containers. See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +TZ=Europe/Amsterdam + +# Any Container environment variables are declared below. See https://docs.docker.com/compose/how-tos/environment-variables/ + +# Required: the token that signs in to the OpenClaw web interface. Generate it with: openssl rand -hex 32 +OPENCLAW_GATEWAY_TOKEN= + +# Optional: model provider keys. Also uncomment the matching line in compose.yaml. +#OPENAI_API_KEY= +#ANTHROPIC_API_KEY= diff --git a/services/openclaw/README.md b/services/openclaw/README.md new file mode 100644 index 00000000..7838e93a --- /dev/null +++ b/services/openclaw/README.md @@ -0,0 +1,51 @@ +# OpenClaw + +[OpenClaw](https://openclaw.ai) is a self-hosted personal AI assistant. Its Gateway runs the agents, connects chat channels, and serves the Control UI web interface. + +This stack runs OpenClaw with a Tailscale sidecar, as described in [the standard setup](../../documentation/standard-setup.md). + +## At a glance + +| Item | Value | +| ------------- | ------------------------------------ | +| Web interface | `https://openclaw..ts.net` | +| Service port | `18789` | +| Image | `ghcr.io/openclaw/openclaw:latest` | +| Data | `./openclaw-data/` | + +## Before you start + +- Set `OPENCLAW_GATEWAY_TOKEN` in `.env`. It is the token that signs in to the web interface. Generate it with `openssl rand -hex 32`. Compose stops with an error until it is set. +- Create the data folder before the first start, so that it is owned by you and not by root: + + ```sh + mkdir -p openclaw-data/state + ``` + + The image runs as user `node` (uid 1000). If your user has another uid, run `sudo chown -R 1000:1000 openclaw-data` too. Without this, Docker creates the folder as root and the application restarts in a loop, logging `GatewayLockError: failed to acquire gateway state ownership`. + +## Deviations from the standard setup + +- **Gateway token.** OpenClaw refuses connections without authentication, so `OPENCLAW_GATEWAY_TOKEN` is required. +- **Sidecar instead of OpenClaw's own Tailscale mode.** OpenClaw can manage Tailscale Serve itself, but it runs the `tailscale` command on the host. The image contains no Tailscale, so `gateway.tailscale.mode` stays `off` and the sidecar provides Serve. For the same reason, signing in with the Tailscale identity (`gateway.auth.allowTailscale`) does not work, and you sign in with the token. +- **Trusted proxy and start without setup.** Tailscale Serve connects to the Gateway from `127.0.0.1`. Without `gateway.trustedProxies`, the Gateway answers `403 proxy_attribution_required`. The command therefore sets `gateway.trustedProxies` to `127.0.0.1` and `gateway.mode` to `local` each time the container starts, so the Gateway runs without `openclaw onboard` first. Any other process in the shared network namespace is trusted the same way, which is why the token stays required. It binds with `--bind lan`, as the upstream Docker setup does. +- **Hardening.** The application drops `NET_RAW` and `NET_ADMIN` and sets `no-new-privileges`, as upstream's `docker-compose.yml` does. +- **No CLI container.** Upstream's compose file has a separate `openclaw-cli` container. Run the CLI in the application container instead, for example `docker exec app-openclaw node dist/index.js `. +- **Gateway port on the Tailnet.** The Gateway also listens on `18789` without HTTPS, reachable at the Tailscale IP address of the device, when your Tailnet policy allows it. It requires the token. + +## First run + +1. Open the web interface and enter the value of `OPENCLAW_GATEWAY_TOKEN` in the settings. +2. Each new browser connects as a new device, and the Gateway asks you to approve it once. List the request and approve it: + + ```sh + docker exec app-openclaw node dist/index.js devices list + docker exec app-openclaw node dist/index.js devices approve + ``` + +3. Add a model provider, for example with an API key, before you chat with an agent. See the [OpenClaw documentation](https://docs.openclaw.ai). + +## Links + +- [OpenClaw documentation](https://docs.openclaw.ai) +- [OpenClaw source code](https://github.com/openclaw/openclaw) diff --git a/services/openclaw/compose.yaml b/services/openclaw/compose.yaml new file mode 100644 index 00000000..86ccd032 --- /dev/null +++ b/services/openclaw/compose.yaml @@ -0,0 +1,82 @@ +configs: + ts-serve: + content: | + {"TCP":{"443":{"HTTPS":true}}, + "Web":{"$${TS_CERT_DOMAIN}:443": + {"Handlers":{"/": + {"Proxy":"http://127.0.0.1:18789"}}}}, + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} + +services: +# Make sure you have updated/checked the .env file with the correct variables. +# Every variable used in this file must be defined there, unless it has a default such as ${VAR:-value}. + # Tailscale Sidecar Configuration + tailscale: + image: tailscale/tailscale:latest # Image to be used + container_name: tailscale-${SERVICE} # Name for local container management + hostname: ${SERVICE} # Name used within your Tailscale environment + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required + - TS_USERSPACE=false + - TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz" + - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The : for the healthz endpoint + #- TS_ACCEPT_DNS=true # Uncomment only if the service must resolve MagicDNS names - this replaces Docker DNS, so Compose service names no longer resolve + - TS_AUTH_ONCE=true + configs: + - source: ts-serve + target: /config/serve.json + volumes: + - ./config:/config # Config folder used to store Tailscale files - you may need to change the path + - ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path + devices: + - /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work + cap_add: + - net_admin # Tailscale requirement + #ports: + # - 0.0.0.0:${SERVICEPORT}:${SERVICEPORT} # Binding the service port to the local network - may be removed if only exposure to your Tailnet is required + # If any DNS issues arise, use your preferred DNS provider by uncommenting the config below + #dns: + # - ${DNS_SERVER} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational + interval: 1m # How often to perform the check + timeout: 10s # Time to wait for the check to succeed + retries: 3 # Number of retries before marking as unhealthy + start_period: 10s # Time to wait before starting health checks + restart: always + + # Application + application: + image: ${IMAGE_URL} # Image to be used + network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale + container_name: app-${SERVICE} # Name for local container management + environment: # Variables are declared in .env file. + - TZ=${TZ} + - OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN:?Set OPENCLAW_GATEWAY_TOKEN in .env} + - OPENCLAW_GATEWAY_PORT=18789 + # Model provider keys are optional. To use one, set it in .env and uncomment the matching line below. + #- OPENAI_API_KEY=${OPENAI_API_KEY} + #- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + # Tailscale Serve reaches the gateway from 127.0.0.1, so that address is a trusted proxy. Without it the gateway answers 403 proxy_attribution_required. + # gateway.mode=local lets the gateway start without running "openclaw onboard" first. + command: + - sh + - -c + - | + node dist/index.js config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.trustedProxies","value":["127.0.0.1"]}]' + exec node dist/index.js gateway --bind lan --port 18789 + init: true + cap_drop: + - NET_RAW + - NET_ADMIN + security_opt: + - no-new-privileges:true + volumes: + - ./${SERVICE}-data/state:/home/node/.openclaw # Settings, workspace and sessions - owned by uid 1000 + depends_on: + tailscale: + condition: service_healthy + # Healthcheck: defined by the image (node dist/docker-healthcheck.js, which checks /healthz), so this file does not override it. + restart: always From 4f13dce4c8c3262d2fb18845b9f53956a0de117a Mon Sep 17 00:00:00 2001 From: Jack Spiering <46534141+jackspiering@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:48:28 +0200 Subject: [PATCH 2/2] OpenClaw: shorten the README row --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 128fc588..6880cdb6 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Every stack starts from the same [standard setup](documentation/standard-setup.m | **Nanote** | Note-taking app that stores notes as Markdown files in folders. | [Details](services/nanote) | | **NextExplorer** | Web file manager for browsing, uploading, and editing files in one folder on your server. | [Details](services/next-explorer) | | **Open WebUI** | Chat interface for AI models, through Ollama or OpenAI-compatible APIs. | [Details](services/open-webui) | -| **OpenClaw** | Personal AI assistant that runs agents and connects them to your chat apps. | [Details](services/openclaw) | +| **OpenClaw** | Personal AI assistant that connects agents to your chat apps. | [Details](services/openclaw) | | **Paperless-ngx** | Document management system that turns scans and files into a searchable archive. | [Details](services/paperless) | | **Pingvin Share** | Archived. File sharing platform with links that can expire. | [Details](services/pingvin-share) | | **Radicale** | CalDAV and CardDAV server that syncs calendars, to-do lists, and contacts. | [Details](services/radicale) |