This guide provides an Ansible playbook to deploy OpenClaw with Docker Compose on Debian 10+, Ubuntu 20.04+, and RHEL 9+ compatible hosts. OpenClaw is an open-source personal AI agent that runs on your own machine and communicates through chat apps like WhatsApp, Telegram, Discord, Slack, and iMessage.
Note: OpenClaw does not provide official pre-built Docker images. This playbook builds the Docker image locally from source. The GitHub repository shows “PACKAGES 0” at ghcr.io/openclaw/openclaw.
Official Resources:
---
- name: Deploy OpenClaw with Docker
hosts: openclaw
become: true
vars:
app_root: /opt/openclaw
app_name: openclaw
app_port: 18789
bridge_port: 18790
openclaw_repo: "https://github.com/openclaw/openclaw.git"
openclaw_version: "main"
gateway_bind: "lan"
# Generate gateway token if not provided
gateway_token: "{{ vault_openclaw_gateway_token | default(lookup('password', '/dev/null length=64 chars=hexdigits')) }}"
tasks:
- name: Install Docker on Debian/Ubuntu
apt:
name:
- docker.io
- docker-compose-plugin
- git
- nodejs
- npm
state: present
update_cache: true
when: ansible_os_family == "Debian"
- name: Install Docker on RHEL family
dnf:
name:
- docker
- docker-compose-plugin
- git
- nodejs
- npm
state: present
when: ansible_os_family == "RedHat"
- name: Enable and start Docker
service:
name: docker
state: started
enabled: true
- name: Add user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: true
- name: Create application directory
file:
path: "{{ app_root }}"
state: directory
mode: "0755"
- name: Clone OpenClaw repository
git:
repo: "{{ openclaw_repo }}"
dest: "{{ app_root }}"
version: "{{ openclaw_version }}"
force: true
- name: Create config and workspace directories
file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ app_root }}/config"
- "{{ app_root }}/workspace"
- name: Write environment file
copy:
dest: "{{ app_root }}/.env"
mode: "0600"
content: |
# Gateway access token
OPENCLAW_GATEWAY_TOKEN={{ gateway_token }}
# Gateway ports
OPENCLAW_GATEWAY_PORT={{ app_port }}
OPENCLAW_BRIDGE_PORT={{ bridge_port }}
# Bind address
OPENCLAW_GATEWAY_BIND={{ gateway_bind }}
# Volume paths
OPENCLAW_CONFIG_DIR={{ app_root }}/config
OPENCLAW_WORKSPACE_DIR={{ app_root }}/workspace
# Docker image (build locally from repository)
OPENCLAW_IMAGE=openclaw:local
- name: Write Docker Compose file
copy:
dest: "{{ app_root }}/docker-compose.yml"
mode: "0644"
content: |
version: '3.8'
services:
openclaw-gateway:
image: ${OPENCLAW_IMAGE:-openclaw:local}
container_name: openclaw-gateway
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
ports:
- "${OPENCLAW_GATEWAY_PORT}:18789"
- "${OPENCLAW_BRIDGE_PORT}:18790"
volumes:
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
environment:
- HOME=/home/node
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
command: ["node", "dist/index.js", "gateway", "--bind", "${OPENCLAW_GATEWAY_BIND:-lan}", "--port", "18789"]
openclaw-cli:
image: ${OPENCLAW_IMAGE:-openclaw:local}
container_name: openclaw-cli
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
volumes:
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
environment:
- HOME=/home/node
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
- BROWSER=echo
entrypoint: ["node", "dist/index.js"]
stdin_open: true
tty: true
- name: Pull Docker image
command: docker compose pull
args:
chdir: "{{ app_root }}"
register: pull_result
- name: Start application stack
command: docker compose up -d
args:
chdir: "{{ app_root }}"
- name: Wait for application to be ready
uri:
url: "http://localhost:{{ app_port }}"
method: GET
status_code:
- 200
- 301
- 302
- 401
retries: 30
delay: 10
until: result is succeeded
register: result
ignore_errors: true
- name: Display gateway token
debug:
msg: |
OpenClaw deployment complete!
Access the Web UI at: http://{{ inventory_hostname }}:{{ app_port }}/?token={{ gateway_token }}
IMPORTANT: Store this token securely. Do not expose port {{ app_port }} to the public internet.
Save the playbook as deploy-openclaw-docker.yml.
Create an inventory file (inventory.ini):
[openclaw]
server1.example.com
server2.example.com
[openclaw:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519
Use Ansible Vault to store sensitive values:
# Create vault file
ansible-vault create group_vars/openclaw/vault.yml
Add your gateway token:
vault_openclaw_gateway_token: your-secure-random-token-here
# Basic run
ansible-playbook -i inventory.ini deploy-openclaw-docker.yml
# With vault
ansible-playbook -i inventory.ini deploy-openclaw-docker.yml --ask-vault-pass
---
- name: Deploy OpenClaw with Model Configuration
hosts: openclaw
become: true
vars:
app_root: /opt/openclaw
app_port: 18789
gateway_token: "{{ vault_openclaw_gateway_token }}"
# Model Configuration
llm_provider: "anthropic"
api_key: "{{ vault_openclaw_api_key }}"
model: "claude-sonnet-4-20250514"
tasks:
- name: Install Docker
apt:
name:
- docker.io
- docker-compose-plugin
- git
state: present
update_cache: true
when: ansible_os_family == "Debian"
- name: Enable and start Docker
service:
name: docker
state: started
enabled: true
- name: Clone OpenClaw repository
git:
repo: "https://github.com/openclaw/openclaw.git"
dest: "{{ app_root }}"
force: true
- name: Create directories
file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ app_root }}/config"
- "{{ app_root }}/workspace"
- name: Write environment file
copy:
dest: "{{ app_root }}/.env"
mode: "0600"
content: |
OPENCLAW_GATEWAY_TOKEN={{ gateway_token }}
OPENCLAW_GATEWAY_PORT={{ app_port }}
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_CONFIG_DIR={{ app_root }}/config
OPENCLAW_WORKSPACE_DIR={{ app_root }}/workspace
OPENCLAW_IMAGE=openclaw:local
- name: Write OpenClaw configuration
copy:
dest: "{{ app_root }}/config/openclaw.json"
mode: "0640"
content: |
{
"gateway": {
"mode": "lan",
"port": {{ app_port }},
"auth": {
"mode": "token",
"token": "{{ gateway_token }}"
}
},
"models": {
"providers": {
"{{ llm_provider }}": {
"apiKey": "{{ api_key }}"
}
},
"default": "{{ model }}"
},
"channels": {}
}
- name: Pull and start
shell: |
docker compose pull
docker compose up -d
args:
chdir: "{{ app_root }}"
---
- name: Deploy OpenClaw with Nginx
hosts: openclaw
become: true
vars:
app_root: /opt/openclaw
app_port: 18789
nginx_port: 80
domain_name: "openclaw.example.com"
gateway_token: "{{ vault_openclaw_gateway_token }}"
tasks:
- name: Install Docker
apt:
name:
- docker.io
- docker-compose-plugin
- git
state: present
update_cache: true
when: ansible_os_family == "Debian"
- name: Enable and start Docker
service:
name: docker
state: started
enabled: true
- name: Clone OpenClaw repository
git:
repo: "https://github.com/openclaw/openclaw.git"
dest: "{{ app_root }}"
force: true
- name: Create directories
file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ app_root }}/config"
- "{{ app_root }}/workspace"
- name: Write Docker Compose override (with Nginx)
copy:
dest: "{{ app_root }}/docker-compose.override.yml"
mode: "0644"
content: |
version: '3.8'
services:
openclaw-gateway:
expose:
- "18789"
nginx:
image: nginx:alpine
container_name: openclaw-nginx
restart: unless-stopped
ports:
- "{{ nginx_port }}:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- openclaw-gateway
- name: Write Nginx configuration
copy:
dest: "{{ app_root }}/nginx.conf"
mode: "0644"
content: |
events {
worker_connections 1024;
}
http {
upstream openclaw {
server openclaw-gateway:18789;
}
server {
listen 80;
server_name {{ domain_name }};
location / {
proxy_pass http://openclaw;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
- name: Pull and start
shell: |
docker compose pull
docker compose up -d
args:
chdir: "{{ app_root }}"
~/.openclaw/openclaw.json| Variable | Default | Description |
|---|---|---|
app_root |
/opt/openclaw |
Application installation directory |
app_name |
openclaw |
Container name |
app_port |
18789 |
Port to expose OpenClaw gateway |
bridge_port |
18790 |
Bridge port for messaging |
openclaw_repo |
https://github.com/openclaw/openclaw.git |
Git repository URL |
openclaw_version |
main |
Git branch or tag to deploy |
gateway_token |
Auto-generated | Gateway access token (use Ansible Vault) |
gateway_bind |
lan |
Bind address (lan, localhost, 0.0.0.0) |
| Provider | Config Key | Notes |
|---|---|---|
| Anthropic | anthropic |
Claude models |
| OpenAI | openai |
GPT-4, GPT-4o, GPT-5 |
| Ollama | ollama |
Local models |
| MiniMax | minimax |
MiniMax models |
| Mistral | mistral |
Mistral models |
| OpenRouter | openrouter |
Multi-provider aggregator |
| Platform | Setup Method |
|---|---|
| Telegram | Bot token from @BotFather |
| QR code pairing (WhatsApp Web protocol) | |
| Discord | Bot token from Discord Developer Portal |
| Slack | App token + bot token |
| iMessage | macOS only, requires additional setup |
| Signal | Via signal-cli |
| Mattermost | Plugin installation |
# SSH to server
ssh user@server
# Check container status
docker compose ps -f /opt/openclaw/docker-compose.yml
# View logs
docker compose logs -f openclaw-gateway
# Access via browser
http://YOUR_SERVER_IP:18789/?token=<your-gateway-token>
# SSH to the server
ssh user@server
# View the token
cat /opt/openclaw/.env | grep OPENCLAW_GATEWAY_TOKEN
---
- name: Backup OpenClaw configuration
hosts: openclaw
become: true
vars:
app_root: /opt/openclaw
tasks:
- name: Create backup directory
file:
path: "{{ app_root }}/backups"
state: directory
- name: Backup configuration
archive:
path:
- "{{ app_root }}/config"
- "{{ app_root }}/workspace"
- "{{ app_root }}/.env"
dest: "{{ app_root }}/backups/config-{{ ansible_date_time.date }}.tar.gz"
---
- name: Update OpenClaw
hosts: openclaw
become: true
vars:
app_root: /opt/openclaw
tasks:
- name: Pull latest code
git:
repo: "{{ openclaw_repo }}"
dest: "{{ app_root }}"
force: true
- name: Rebuild Docker image
command: docker compose build
args:
chdir: "{{ app_root }}"
- name: Restart application
command: docker compose up -d --force-recreate
args:
chdir: "{{ app_root }}"
# SSH to server
ssh user@server
# Check container status
docker compose ps -f /opt/openclaw/docker-compose.yml
# View logs
docker compose logs -f openclaw-gateway
docker compose -f /opt/openclaw/docker-compose.yml restart
cd /opt/openclaw
docker compose pull
docker compose up -d
Build fails:
# Check Node.js version
node -v # Must be v22+
# Check Docker logs
docker compose logs
Gateway won’t start:
# Check configuration
cat /opt/openclaw/config/openclaw.json
# Verify token
cat /opt/openclaw/.env | grep OPENCLAW_GATEWAY_TOKEN
For detailed security guidance, see OpenClaw Security.
Any questions?
Feel free to contact us. Find all contact information on our contact page.