OpenClaw Setup Guide
Full setup guide for running OpenClaw via Docker with Telegram, google-antigravity provider, and Join39 agent network integration. Follow each step in order.
Architecture
Telegram Bot
|
v
Join39 / External --> Cloudflare Tunnel (HTTPS)
|
v
OpenClaw Gateway (HTTP :18789)
|
+--> /join39 (Join39 plugin route)
+--> /v1/chat/completions (OpenAI-compatible API)
+--> /overview (Control UI)
+--> WebSocket (Telegram, CLI, etc.)
|
v
AI Provider (google-antigravity / Gemini Flash)
Prerequisites
Before you start, make sure you have:
- macOS or Linux (Windows users: use WSL2)
- Docker Desktop installed and running
- Node.js 22+ installed
- cloudflared - install with
brew install cloudflared(macOS) or see Cloudflare docs for Linux - A Telegram account (optional, for Telegram bot)
- A Join39 account (optional, for agent network)
Step 1: Clone and Build
git clone https://github.com/nebullii/openclaw.git
cd openclaw
Build the Docker image (this may take a few minutes):
docker build -t openclaw:local .
Step 2: Create Your Config Directory
The .openclaw/ directory holds your gateway config, extensions, and agent data. It is gitignored (your secrets stay local).
mkdir -p .openclaw/extensions
Copy the example config:
cp openclaw.example.json .openclaw/openclaw.json
Create the workspace directory:
mkdir -p workspace
Step 3: Set Up Environment Variables
cp .env.example .env
Open .env in your editor and fill in the values. Here's what each one does:
| Variable | Required? | Description | |---|---|---| | OPENCLAW_IMAGE | Yes | Docker image name. Use openclaw:local if you built it in Step 1 | | OPENCLAW_GATEWAY_TOKEN | Yes | Pick any random string. This is the password for the Control UI and CLI | | OPENCLAW_CONFIG_DIR | Yes | Path to your .openclaw/ directory. Use ./.openclaw | | OPENCLAW_WORKSPACE_DIR | Yes | Path to your workspace directory. Use ./workspace | | OPENCLAW_GATEWAY_PORT | No | Gateway port (default: 18789) | | OPENCLAW_GATEWAY_BIND | No | lan (accessible from network) or loopback (localhost only) | | JOIN39_PASSPORT_URL | No | Your Join39 passport token (needed for Join39 integration) | | CLOUDFLARE_TUNNEL_TOKEN | No | Only for persistent Cloudflare tunnels |
Step 4: Configure the Gateway
Open .openclaw/openclaw.json in your editor. The example file has everything pre-configured, but you need to update these values:
4a. Set your gateway token
Find gateway.remote.token and replace "your-gateway-token-here" with the same token you put in OPENCLAW_GATEWAY_TOKEN in .env. These must match.
4b. Set up your AI provider
The example uses google-antigravity with Gemini Flash. Update the email in auth.profiles:
"google-antigravity:your-email@gmail.com": {
"provider": "google-antigravity",
"mode": "oauth"
}
Replace your-email@gmail.com with your actual Google account email.
4c. Set up Telegram (optional)
If you want a Telegram bot, get a token from @BotFather on Telegram and update:
"channels": {
"telegram": {
"enabled": true,
"botToken": "your-bot-token-from-botfather"
}
}
If you don't want Telegram, set "enabled": false.
Step 5: Start the Gateway
docker compose up -d openclaw-gateway
Check that it started successfully:
docker compose logs openclaw-gateway --tail 20
You should see a line like listening on ws://0.0.0.0:18789.
Open the Control UI
Go to http://localhost:18789/overview in your browser. Paste your OPENCLAW_GATEWAY_TOKEN to connect.
Step 6: Device Pairing (CLI)
To send messages via the CLI:
docker compose run --rm openclaw-cli message send "hello"
If it says "pairing required", approve the device:
docker compose exec openclaw-gateway node dist/index.js devices list
docker compose exec openclaw-gateway node dist/index.js devices approve <device-id>
Replace <device-id> with the ID shown in the list.
Step 7: Join39 Integration (Optional)
Skip this step if you don't need Join39.
7a. What is Join39?
Join39 is ClawNanda's agent network. Other AI agents can discover and call your agent. It sends function calls as HTTPS POST requests and expects JSON responses within 10 seconds / 2000 characters.
7b. Copy the extension
cp -r extensions/join39 .openclaw/extensions/join39
This copies the plugin files into your config directory. The extension contains:
| File | Purpose | |---|---| | package.json | Extension metadata | | openclaw.plugin.json | Plugin manifest (gateway crashes without this!) | | index.ts | Entry point - registers the /join39 HTTP route | | src/config.ts | Config parsing | | src/handler.ts | Handles incoming requests, forwards to your agent | | src/types.ts | TypeScript types |
7c. Verify plugin config
The openclaw.example.json already includes the Join39 plugin config. Verify these sections exist in your .openclaw/openclaw.json:
"gateway": {
"http": {
"endpoints": {
"chatCompletions": { "enabled": true }
}
}
}
"plugins": {
"load": {
"paths": ["/home/node/.openclaw/extensions/join39"]
},
"entries": {
"join39": {
"enabled": true,
"config": {
"enabled": true,
"webhookPath": "/join39",
"agentId": "main",
"timeoutMs": 30000
}
}
}
}
7d. Restart the gateway
docker compose restart openclaw-gateway
Verify the plugin loaded:
docker compose logs openclaw-gateway 2>&1 | grep "join39"
You should see: [join39] registered HTTP route at /join39
7e. Start the Cloudflare Tunnel
Join39 requires HTTPS. Cloudflare Tunnel exposes your local gateway over a secure URL for free.
Quick tunnel (free, URL changes every restart):
cloudflared tunnel --url http://localhost:18789
It will print a URL like https://random-words.trycloudflare.com. Your Join39 endpoint is that URL + /join39.
Persistent tunnel (requires a Cloudflare domain):
Add your tunnel token to .env as CLOUDFLARE_TUNNEL_TOKEN, then:
docker compose --profile tunnel up -d cloudflared
7f. Test it
Replace <TUNNEL_URL> with your actual Cloudflare tunnel URL:
source .env && curl -s -X POST "<TUNNEL_URL>/join39" -H "Content-Type: application/json" -H "Authorization: Bearer ${JOIN39_PASSPORT_URL}" -d '{"function":"ping","parameters":{"message":"hello"}}'
You should get a response like:
{"result":"pong! Hello there! What can I help you with?","error":false}
7g. Register your app on Join39
1. Go to https://join39.org/apps/submit 2. Fill in:
- Name: pick a URL-safe slug (e.g.
my-openclaw-agent) - Display name: your agent's name
- Description: what your agent does
- Category:
utilitiesorproductivity - API endpoint:
<TUNNEL_URL>/join39 - HTTP method:
POST - Auth type:
bearer
3. For the function parameters schema, use:
{
"type": "object",
"properties": {
"function": {
"type": "string",
"description": "The function to call, e.g. chat, ping, ask"
},
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The message or query to send to the agent"
}
},
"required": ["message"]
}
},
"required": ["function", "parameters"]
}
4. Submit and install the app on your agent
File Structure Overview
openclaw/
├── .env.example # Template for environment variables
├── .env # Your actual env vars (gitignored)
├── openclaw.example.json # Template for gateway config
├── docker-compose.yml # Docker services definition
├── Dockerfile # Build instructions
├── SETUP.md # This guide
├── extensions/
│ └── join39/ # Join39 extension (repo reference copy)
├── .openclaw/ # Your config directory (gitignored)
│ ├── openclaw.json # Gateway config (copied from example)
│ └── extensions/
│ └── join39/ # Join39 extension (active copy)
└── workspace/ # Agent workspace
Docker Compose Services
| Service | Description | Command | |---|---|---| | openclaw-gateway | Main gateway (always running) | docker compose up -d openclaw-gateway | | openclaw-cli | CLI for sending messages | docker compose run --rm openclaw-cli message send "hi" | | cloudflared | Cloudflare tunnel (opt-in) | docker compose --profile tunnel up -d cloudflared |
Common Commands
# Start the gateway
docker compose up -d openclaw-gateway
# Stop everything
docker compose down
# View gateway logs
docker compose logs openclaw-gateway --tail 50
# Restart after config changes
docker compose restart openclaw-gateway
# Start quick tunnel
cloudflared tunnel --url http://localhost:18789
# Send a message via CLI
docker compose run --rm openclaw-cli message send "hello"
Troubleshooting
| Issue | Fix | |---|---| | Cannot find module 'zod' | The Docker container has no node_modules for extensions. Make sure src/config.ts uses plain JS, not zod imports. | | Gateway crash-loops | Make sure openclaw.plugin.json exists in the extension directory. | | 405 on /v1/chat/completions | Set gateway.http.endpoints.chatCompletions.enabled: true in openclaw.json. | | pairing required on Control UI | Paste your OPENCLAW_GATEWAY_TOKEN in the Control UI settings field. | | token_mismatch | Make sure gateway.remote.token in openclaw.json matches OPENCLAW_GATEWAY_TOKEN in .env. | | Volume mount shows stale files | Use docker compose cp <file> openclaw-gateway:<path> to force-copy into the container, then restart. | | Quick tunnel URL changed | You get a new URL every time you restart cloudflared. Update it on Join39. | | Plugin not loading | Run docker compose logs openclaw-gateway 2>&1 \| grep join39 to see errors. |
Links
- OpenClaw Docs
- Join39 Developer Docs
- ClawNanda
- Cloudflare Tunnels
- @BotFather (Telegram bot tokens)





