Tutorial: Secure OpenClaw with CloudConnexa (Docker — Advanced)⁠

Overview⁠

This guide shows how to securely deploy OpenClaw using Docker and make it accessible through CloudConnexa without exposing any public ports.

This setup uses:

  • CloudConnexa Host Connector — provides private, always-on access.

  • Caddy reverse proxy — enables HTTPS access.

  • Docker Compose — runs OpenClaw and supporting services.

  • Security hardening — based on OpenClaw best practices.

OpenClaw runs inside a container and is only accessible through CloudConnexa.

Architecture⁠

[Authorized CloudConnexa Users]
        ↓ (Tunnel)
[CloudConnexa]
        ↓
[Host Connector on your VM] ← always-on tunnel, stable IP (e.g., 100.96.x.x)
        ↓
[Caddy Reverse Proxy] ← HTTPS/TCP443, shared network namespace with OpenClaw gateway
        ↓
[OpenClaw Gateway] ← localhost:18789 inside container, token auth + device pairing
        ↓
[Claude / OpenAI / ... APIs]

Before you begin⁠

Ensure you have the following:

  • A Linux VM (Ubuntu 22.04/24.04 recommended) with at least 2 GB RAM (4 GB recommended) and 10 GB disk space.

  • Root or sudo access.

  • A CloudConnexa account.

  • AI provider API key (OpenAI, Anthropic, etc.)

Step 1: Install Docker⁠

  1. Install Docker Engine using the official documentation: Docker Install.

  2. Verify the installation:

    docker --version
    docker compose version
    • Expected results:

      docker --version
      # Should show: Docker version 20.10+ or higher
      
      docker compose version
      # Should show: Docker Compose version v2.x.x or higher

Step 2: Create a host in CloudConnexa⁠

The CloudConnexa host connector installs directly on your application server, enabling access to applications running on that machine.

  1. Sign in to your WPC's Administration portal by entering your Cloud ID in a web browser (for example, company_name.openvpn.com).

  2. Navigate to Hosts → Hosts.

  3. Click Add Host.

  4. Configure:

    • Name: OpenClaw

    • Domain Name: openclaw.local (CloudConnexa will automatically resolve this domain to the Connector's tunnel IP for users connected to the WPC.)

    • Connector name

    • Region

  5. Click Finish.

Step 3: Deploy the host connector⁠

  1. Open the new Host, and click the Connectors tab.

  2. Click Deploy → Deploy Connector.

  3. Select your Linux distribution running on your VM.

  4. Run the provided script on your VM's terminal.

  5. Paste the token shown under "Generate Token" (located below the script).

  6. Click Next.

  7. Verify that the Connector shows "Connected."

  8. Record the tunnel IP shown next to the Connector (e.g., 100.96.1.50) for a later step.

Step 4: Configure applications (optional)⁠

Define which services are exposed so that granular access control can be configured.

  1. Open the new Host, and click the Applications tab.

  2. Click Add Application.

  3. Add an application for HTTPS:

    • Name: OpenClaw HTTPS

    • Protocol: TCP

    • Port: 443

  4. (Optional) Add an application for HTTP (useful for curl testing from the tunnel):

    • Name: OpenClaw HTTP

    • Protocol: TCP

    • Port: 80

  5. Save and ensure the appropriate Access Groups have permission to reach these Applications.

Step 6: Prepare the project directory⁠

  1. Prepare the project directory on Docker:

    mkdir -p ~/openclaw-docker
    cd ~/openclaw-docker
    mkdir -p config workspace
  2. Set ownership to uid 1000 (the node user inside the container) and lock down permissions:

    sudo chown -R 1000:1000 config workspace
    chmod 700 config workspace

    Keep ~/openclaw-docker/config/ and ~/openclaw-docker/workspace/ at 700 to prevent other users or processes from reading tokens and credentials.

Step 7: Create the Caddyfile⁠

Caddy shares the gateway container's network namespace, so it can reach the gateway at localhost:18789. It serves two listeners:

  • Port 80 (HTTP): For local curl testing from the host.

  • Port 443 (HTTPS): For browser access through CloudConnexa. Uses tls internal to auto-generate a self-signed certificate for the hostname.

  1. Create the Caddyfile in the project directory:

    tee Caddyfile > /dev/null << 'EOF'
    :80 {
        reverse_proxy localhost:18789
    }
    
    openclaw.local {
        tls internal
        reverse_proxy localhost:18789
    }
    EOF
    1. Replace openclaw.local with your Host's domain name if different.

Step 8: Create docker-compose.yml⁠

This Compose file is built from the official OpenClaw Docker documentation and the upstream docker-compose.yml in the OpenClaw repository, with Caddy added as a reverse proxy.

  1. Detect the CloudConnexa tunnel IP and store it for use in the compose file:

    TUNNEL_IP=$(sudo openvpn3 sessions-list | grep 'tun' | awk -F ':' '{print $3}') && \
    TUNNEL_IP=$(ip -4 addr show $TUNNEL_IP | grep -oP '(?:\b\.?(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){4}' | head -1)
    
    echo "Detected tunnel IP: $TUNNEL_IP"
  2. Verify this matches the tunnel IP shown in the CloudConnexa Admin portal (Step 3: Deploy the host connector). If it doesn't, set it manually:

    TUNNEL_IP='100.96.1.50'
  3. Create the docker-compose.yml file using the detected tunnel IP:

    tee docker-compose.yml > /dev/null << EOF
    services:
      openclaw-gateway:
        image: ghcr.io/openclaw/openclaw:latest
        container_name: openclaw-gateway
        restart: unless-stopped
        init: true
        ports:
          - "127.0.0.1:80:80"        # HTTP — local access for curl testing
          - "$TUNNEL_IP:443:443"     # HTTPS — CloudConnexa tunnel access
        volumes:
          - ./config:/home/node/.openclaw
          - ./workspace:/home/node/.openclaw/workspace
        environment:
          HOME: /home/node
          TERM: xterm-256color
        healthcheck:
          test:
            [
              "CMD",
              "node",
              "-e",
              "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
            ]
          interval: 30s
          timeout: 5s
          retries: 5
          start_period: 20s
    
      openclaw-cli:
        image: ghcr.io/openclaw/openclaw:latest
        container_name: openclaw-cli
        profiles: ["cli"]
        network_mode: "service:openclaw-gateway"
        cap_drop:
          - NET_RAW
          - NET_ADMIN
        security_opt:
          - no-new-privileges:true
        stdin_open: true
        tty: true
        init: true
        entrypoint: ["node", "dist/index.js"]
        volumes:
          - ./config:/home/node/.openclaw
          - ./workspace:/home/node/.openclaw/workspace
        environment:
          HOME: /home/node
          TERM: xterm-256color
          BROWSER: echo
        depends_on:
          - openclaw-gateway
    
      caddy:
        image: caddy:latest
        container_name: caddy
        restart: unless-stopped
        network_mode: "service:openclaw-gateway"
        volumes:
          - ./Caddyfile:/etc/caddy/Caddyfile:ro
          - caddy_data:/data
        depends_on:
          - openclaw-gateway
    
    volumes:
      caddy_data:
    EOF

Step 9: Run onboarding and start services⁠

9.1 Run onboarding⁠

  1. Run the onboarding wizard:

    docker compose run --rm openclaw-cli onboard
  2. Follow the prompts to:

    • Select your AI provider.

    • Enter your API key.

    • Set onboarding mode to Manual.

    • Use default values for Config handling.

    • Set Gateway mode to Local.

    • Leave Gateway bind as default (loopback).

9.2 Retrieve the gateway token⁠

The gateway token is displayed at the end of the onboarding wizard. Copy it for use later.

If you need to find it again:

  1. Run:

    openclaw dashboard --no-open
  2. Copy the token value from:

    Dashboard URL: http://127.0.0.1:18789/#token=

9.3 Start all services⁠

Start the OpenClaw gateway and Caddy reverse proxy.

  1. Start all services defined in the Docker Compose file and run them in the background:

    docker compose up -d
  2. Verify the services are running:

    docker compose ps
    • Expected output:

      NAME                IMAGE                              STATUS
      caddy               caddy:latest                       Up
      openclaw-gateway    ghcr.io/openclaw/openclaw:latest   Up (healthy)

9.4 Test local access⁠

Verify that Caddy is correctly proxying requests to the OpenClaw gateway.

  1. Confirm the reverse proxy is working:

    curl -s http://127.0.0.1/

    You should see the OpenClaw Control UI HTML (starting with <!doctype html>).

Step 10: Apply security hardening⁠

This step applies recommended security settings for running OpenClaw behind CloudConnexa and a reverse proxy.

10.1 Enable gateway authentication (token mode)⁠

Gateway auth is required by default; if no token is configured, the gateway refuses WebSocket connections (fail-closed).

  1. Verify the current authentication mode:

    Gateway auth is required by default; if no token is configured, the gateway refuses WebSocket connections (fail-closed).

    Expected result: token.

  2. If not set, enable token authentication:

    docker compose run --rm openclaw-cli config set gateway.auth.mode token

10.2 Configure trusted proxy⁠

Since Caddy is acting as a reverse proxy, OpenClaw must trust it to correctly identify client requests.

  1. Allow traffic forwarded from localhost (Caddy) and ensure client IPs and requests are handled correctly:

    docker compose run --rm openclaw-cli config set gateway.trustedProxies '["127.0.0.1"]' --strict-json

10.3 Configure allowed origins⁠

The Control UI rejects requests from origins not in its allowlist.

  1. Configure the allowed HTTPS domain and tunnel IP used by CloudConnexa:

    TUNNEL_IP=$(sudo openvpn3 sessions-list | grep 'tun' | awk -F ':' '{print $3}') && \
    TUNNEL_IP=$(ip -4 addr show $TUNNEL_IP | grep -oP '(?:\b\.?(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){4}' | head -1)
    
    openclaw config set gateway.controlUi.allowedOrigins "[\"https://$TUNNEL_IP\",\"https://openclaw.local\"]" --strict-json
    1. Replace openclaw.local with your Host domain if different.

10.4 (Optional) Disable mDNS discovery⁠

The gateway broadcasts its presence via mDNS on the local network by default, which can leak operational details like filesystem paths and SSH port availability.

  1. Disable local network discovery:

    docker compose run --rm openclaw-cli config set discovery.mdns.mode off

10.5 (Optional) Enable log redaction⁠

Redaction prevents sensitive information from appearing in logs.

  1. Mask sensitive tool output in logs:

    docker compose run --rm openclaw-cli config set logging.redactSensitive tools

10.6 Apply configuration changes⁠

  1. Restart all services to apply the updated settings:

    docker compose down && docker compose up -d

Step 11: (Optional) Configure firewall hardening⁠

Docker-published container ports bypass the host's INPUT iptables rules. Even though we bind ports to specific IPs, it's good practice to enforce rules in the DOCKER-USER chain as defense-in-depth.

  1. Add firewall rules that restrict container traffic, allow localhost and CloudConnexa tunnel traffic, and block other inbound connections:

    sudo tee -a /etc/ufw/after.rules > /dev/null << 'EOF'
    *filter
    :DOCKER-USER - [0:0]
    -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
    -A DOCKER-USER -s 127.0.0.0/8 -j RETURN
    -A DOCKER-USER -s 100.96.0.0/11 -j RETURN
    -A DOCKER-USER -m conntrack --ctstate NEW -j DROP
    -A DOCKER-USER -j RETURN
    COMMIT
    EOF
  2. Reload firewall rules:

    sudo ufw reload
    sudo iptables -S DOCKER-USER
  3. Verify rules (optional) by displaying active Docker firewall rules:

    sudo iptables -S DOCKER-USER

Step 12: Access OpenClaw remotely⁠

  1. Connect to CloudConnexa.

  2. In a web browser, navigate to:

    https://openclaw.local/#token=<your-token>
    1. Replace openclaw.local with your Host domain if different.

    2. Replace <your-token> with the gateway token from Step 1: Install OpenClaw.

  3. Accept the certificate warning. (Caddy uses a self-signed internal certificate.)

Step 13: Approve the device⁠

On first connection from a new browser, the Control UI will show "pairing required". This is expected; the gateway requires device approval as an additional layer of security on top of token authentication.

  1. Connect to your server.

  2. List pending device requests:

    openclaw devices list
  3. Note the device ID you'll need to approve.

  4. Approve the device:

    openclaw devices approve <requestId>
    1. Replace <requestId> with the ID from the device list.

  5. Refresh the browser.

  6. The Control UI should now load.

Managing your deployment⁠

Troubleshooting⁠