Building Custom AI Skills with InstructLab Taxonomy
Create domain-specific AI capabilities using InstructLab's taxonomy system—from writing skill definitions to generating synthetic training data and validating fine-tuned models.
Getting OpenClaw running on Azure is one thing — keeping it running reliably is another. This post collects the operational lessons I learned after deploying OpenClaw across multiple Azure VMs.
One of the most confusing behaviors is the “connection reset by peer” that happens right after docker compose up -d. Here’s what’s actually going on:
Timeline after docker compose up -d:
0s → Container created, port mapped via docker-proxy
1s → Port is OPEN (docker-proxy listening) but app not ready
2s → curl gets "Connection reset by peer" ← NORMAL
5s → Node.js process binds to port, starts hooks
7s → Gateway fully initialized, serving Control UI
8s → curl returns HTTP 200 ← SUCCESSdocker compose up -d
sleep 10 # Give the gateway time to initialize
curl -I http://127.0.0.1:18789Or use a retry loop:
docker compose up -d
for i in $(seq 1 30); do
if curl -sS -o /dev/null -w "%{http_code}" http://127.0.0.1:18789 2>/dev/null | grep -q "200"; then
echo "Gateway ready after ${i}s"
break
fi
sleep 1
doneThe default docker-compose.yml starts both openclaw-gateway and openclaw-cli as long-running services. The CLI doesn’t need to run as a daemon — it’s a utility tool.
Running docker compose up -d starts both:
openclaw-openclaw-cli-1 Up
openclaw-openclaw-gateway-1 UpThe CLI container consumes resources and can cause volume contention with the gateway.
# Start only the gateway as a daemon
docker compose up -d openclaw-gateway
# Use CLI only when needed (one-shot)
docker compose run --rm openclaw-cli config get
docker compose run --rm openclaw-cli dashboard --no-open
docker compose run --rm openclaw-cli security auditEdit docker-compose.yml:
services:
openclaw-gateway:
# ... existing config ...
openclaw-cli:
profiles: ["cli"]
# ... existing config ...Now docker compose up -d only starts the gateway. To explicitly use the CLI service:
docker compose --profile cli run --rm openclaw-cli config getOr just keep using docker compose run --rm openclaw-cli ... — it works regardless of profiles.
While this series used GitHub Copilot (via device-flow auth), OpenClaw supports multiple LLM backends. Here’s a quick overview:
| Provider | Auth Method | Config Key | Notes |
|---|---|---|---|
| GitHub Copilot | Device flow | (built-in) | Requires Copilot subscription |
| OpenAI | API key | providers.openai.apiKey | GPT-4o, GPT-4 Turbo |
| Anthropic | API key | providers.anthropic.apiKey | Claude family |
| Google Gemini | API key or CLI auth | providers.google.apiKey | Gemini Pro/Ultra |
| Local (Ollama) | No auth | providers.ollama.baseUrl | Self-hosted models |
| Azure OpenAI | API key + endpoint | providers.azureOpenai.* | Enterprise |
# Example: Switch to OpenAI
docker compose run --rm openclaw-cli config set \
providers.openai.apiKey "sk-..."
# Example: Switch to a local Ollama instance
docker compose run --rm openclaw-cli config set \
providers.ollama.baseUrl "http://host.docker.internal:11434"
# Restart to apply
docker compose down
docker compose up -d openclaw-gateway# Check container status
docker compose ps
# Check if gateway is responding
curl -sS -o /dev/null -w "%{http_code}" http://127.0.0.1:18789
# Check gateway logs (last 50 lines)
docker compose logs --tail=50 openclaw-gateway
# Check resource usage
docker stats openclaw-openclaw-gateway-1 --no-streamCreate ~/openclaw/healthcheck.sh:
#!/bin/bash
HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" \
http://127.0.0.1:18789 2>/dev/null)
if [ "$HTTP_CODE" != "200" ]; then
echo "$(date): Gateway unhealthy (HTTP $HTTP_CODE), restarting..."
cd ~/openclaw
docker compose down
docker compose up -d openclaw-gateway
sleep 10
NEW_CODE=$(curl -sS -o /dev/null -w "%{http_code}" \
http://127.0.0.1:18789 2>/dev/null)
echo "$(date): After restart: HTTP $NEW_CODE"
else
echo "$(date): Gateway healthy (HTTP 200)"
fiAdd to cron for periodic checks:
chmod +x ~/openclaw/healthcheck.sh
crontab -e
# Add: */5 * * * * /home/azureuser/openclaw/healthcheck.sh >> /home/azureuser/openclaw/health.log 2>&1| Path (inside container) | Host mount | Contains |
|---|---|---|
/home/node/.openclaw/ | Docker volume | Config, conversation history, device pairs |
~/openclaw/.env | Host filesystem | Gateway token, bind settings, env vars |
# Backup config from the container volume
docker compose run --rm openclaw-cli config get > ~/openclaw-config-backup.json
# Backup the .env file
cp ~/openclaw/.env ~/openclaw/.env.backup.$(date +%Y%m%d)
# Full volume backup
docker run --rm \
-v openclaw_openclaw-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/openclaw-data-$(date +%Y%m%d).tar.gz -C /data .cd ~/openclaw
git pull
docker compose down
docker compose up -d --build --force-recreate openclaw-gateway
docker compose logs --tail=50 openclaw-gatewayNEW_TOKEN=$(openssl rand -hex 32)
sed -i "s/OPENCLAW_GATEWAY_TOKEN=.*/OPENCLAW_GATEWAY_TOKEN=$NEW_TOKEN/" .env
docker compose down
docker compose up -d openclaw-gateway
echo "New token: $NEW_TOKEN"# 1. Fix Discord Developer Portal settings first
# 2. Then re-enable in OpenClaw
docker compose run --rm openclaw-cli config set channels.discord.enabled true
docker compose down
docker compose up -d openclaw-gateway
docker compose logs --tail=100 openclaw-gateway
# Verify no "Fatal Gateway error: 4014"docker compose run --rm openclaw-cli doctor --fixdocker compose run --rm openclaw-cli security auditThose WARN messages about unset CLAUDE_* variables are harmless but noisy:
WARN[0000] The "CLAUDE_AI_SESSION_KEY" variable is not set. Defaulting to a blank string.
WARN[0000] The "CLAUDE_WEB_SESSION_KEY" variable is not set. Defaulting to a blank string.
WARN[0000] The "CLAUDE_WEB_COOKIE" variable is not set. Defaulting to a blank string.cat >> ~/openclaw/.env << 'EOF'
CLAUDE_AI_SESSION_KEY=
CLAUDE_WEB_SESSION_KEY=
CLAUDE_WEB_COOKIE=
EOFAfter this, the warnings disappear from every docker compose command.
# Start gateway only
docker compose up -d openclaw-gateway
# Stop everything
docker compose down
# View logs (live)
docker compose logs -f openclaw-gateway
# View logs (last 100 lines)
docker compose logs --tail=100 openclaw-gateway
# Check status
docker compose ps
# Run CLI command
docker compose run --rm openclaw-cli <command>
# Get current config
docker compose run --rm openclaw-cli config get
# Set a config value
docker compose run --rm openclaw-cli config set <key> <value>
# Generate dashboard URL
docker compose run --rm openclaw-cli dashboard --no-open
# List/approve devices
docker compose run --rm openclaw-cli devices list
docker compose run --rm openclaw-cli devices approve <requestId>
# Security audit
docker compose run --rm openclaw-cli security audit
# Doctor (diagnose + fix)
docker compose run --rm openclaw-cli doctor --fix
# Rebuild from source
docker compose up -d --build --force-recreate openclaw-gatewayThis 10-part series covered the complete journey of deploying OpenClaw on Azure:
Every error message, configuration option, and workaround in this series was verified on a real Azure VM deployment. If you encounter issues not covered here, the openclaw-cli doctor --fix and openclaw-cli security audit commands are your best starting point.
AI & Cloud Advisor with 18+ years experience. Author of 8 technical books, creator of Ansible Pilot. Speaker at KubeCon EU & Red Hat Summit 2026.
Create domain-specific AI capabilities using InstructLab's taxonomy system—from writing skill definitions to generating synthetic training data and validating fine-tuned models.
How to access the OpenClaw Control UI dashboard from an Azure VM — via SSH tunnel (secure) or public IP. Covers device pairing, dashboard authentication, and the browser-based management interface.
End-to-end guide to building a complete persistent memory system for your OpenClaw AI agent. Combine memory flush, hybrid search, file-backed notes, SQLite indexing, and session hooks into a cohesive knowledge architecture.