Post

Self-host OpenClaw with Docker, Traefik, Authentik, and Telegram

A clean fresh-install guide for running OpenClaw on a VPS or homelab server behind Traefik and Authentik, with Telegram integration and no direct exposed app ports.

Self-host OpenClaw with Docker, Traefik, Authentik, and Telegram

OpenClaw is most useful when it can live close to your infrastructure: it can read project files, help maintain services, answer Telegram messages, and act like an operator for your homelab.

This guide shows a fresh self-hosted OpenClaw deployment using Docker Compose, Traefik, Authentik, and a Telegram bot.

The design goal is simple:

1
2
3
4
5
6
7
Internet
  ↓
Traefik :443
  ↓
Authentik SSO
  ↓
OpenClaw Gateway inside Docker

OpenClaw itself does not publish a host port. It only listens inside Docker, and Traefik exposes it through HTTPS.

What we are building

We will deploy:

  • OpenClaw Gateway container
  • A persistent OpenClaw config/state directory
  • A workspace directory for files and projects
  • Traefik HTTPS routing
  • Authentik forward-auth protection
  • Telegram bot integration
  • Optional host-folder access for homelab administration

The public URL in this guide will be:

1
https://agent.example.com

Replace it with your own domain.

Requirements

You need:

  • A Linux server or VPS
  • Docker and Docker Compose
  • A working Traefik stack
  • A Docker network shared with Traefik, for example proxy
  • DNS pointing agent.example.com to your server
  • Optional but recommended: Authentik already deployed behind Traefik
  • A Telegram bot token from BotFather

This guide assumes Traefik already owns ports 80 and 443.

Folder layout

Keep the service self-contained:

1
2
sudo mkdir -p /home/ubuntu/openclaw/.openclaw/workspace
cd /home/ubuntu/openclaw

Recommended structure:

1
2
3
4
5
6
/home/ubuntu/openclaw/
├── .env
├── docker-compose.yml
└── .openclaw/
    ├── openclaw.json
    └── workspace/

Create the environment file

Generate a gateway token. This is the token you will use to access the Web UI.

1
openssl rand -hex 32

Create .env:

1
nano /home/ubuntu/openclaw/.env

Example:

OPENCLAW_CONFIG_DIR=/home/ubuntu/openclaw/.openclaw
OPENCLAW_WORKSPACE_DIR=/home/ubuntu/openclaw/.openclaw/workspace
OPENCLAW_GATEWAY_TOKEN=replace_with_a_long_random_token
OPENCLAW_DISABLE_BONJOUR=1
OPENCLAW_TZ=UTC

Lock it down:

1
chmod 600 /home/ubuntu/openclaw/.env

Never commit this file to Git.

Create the OpenClaw config

Create the config file:

1
nano /home/ubuntu/openclaw/.openclaw/openclaw.json

Minimal example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{
  "gateway": {
    "mode": "local",
    "auth": {
      "mode": "token",
      "token": "replace_with_the_same_token_as_env"
    },
    "remote": {
      "url": "ws://127.0.0.1:18789",
      "token": "replace_with_the_same_token_as_env"
    },
    "port": 18789,
    "bind": "lan",
    "tailscale": {
      "mode": "off",
      "resetOnExit": false
    },
    "controlUi": {
      "allowInsecureAuth": true,
      "allowedOrigins": [
        "http://localhost:18789",
        "http://127.0.0.1:18789",
        "https://agent.example.com"
      ]
    }
  },
  "channels": {
    "telegram": {
      "enabled": true,
      "botToken": "replace_with_telegram_bot_token",
      "allowFrom": [
        "replace_with_your_telegram_user_id"
      ],
      "groups": {
        "*": {
          "requireMention": true
        }
      }
    }
  },
  "plugins": {
    "entries": {
      "bonjour": {
        "enabled": false
      },
      "openai": {
        "enabled": true
      }
    }
  }
}

Important notes:

  • gateway.auth.token, gateway.remote.token, and OPENCLAW_GATEWAY_TOKEN should match.
  • Add your real domain to gateway.controlUi.allowedOrigins.
  • allowFrom should contain only trusted Telegram user IDs.
  • Do not expose your Telegram bot token publicly.

Fix ownership for the container user. The official image normally runs as UID 1000:

1
2
3
sudo chown -R 1000:1000 /home/ubuntu/openclaw/.openclaw
sudo chmod 700 /home/ubuntu/openclaw/.openclaw
sudo chmod 600 /home/ubuntu/openclaw/.openclaw/openclaw.json

Docker Compose

Create docker-compose.yml:

1
nano /home/ubuntu/openclaw/docker-compose.yml

Paste this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
services:
  openclaw-gateway:
    image: ghcr.io/openclaw/openclaw:latest
    container_name: openclaw-gateway
    restart: unless-stopped
    env_file:
      - .env
    environment:
      HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_CONFIG_DIR: /home/node/.openclaw
      OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
      OPENCLAW_DISABLE_BONJOUR: ${OPENCLAW_DISABLE_BONJOUR:-1}
      TZ: ${OPENCLAW_TZ:-UTC}
    volumes:
      - ./.openclaw:/home/node/.openclaw
      - ./.openclaw/workspace:/home/node/.openclaw/workspace
    cap_drop:
      - NET_RAW
      - NET_ADMIN
    security_opt:
      - no-new-privileges:true
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - proxy
    init: true
    command:
      [
        "node",
        "dist/index.js",
        "gateway",
        "--bind",
        "lan",
        "--port",
        "18789",
        "--allow-unconfigured"
      ]
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"

      # HTTP → HTTPS
      - "traefik.http.middlewares.openclaw-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.openclaw.entrypoints=http"
      - "traefik.http.routers.openclaw.rule=Host(`agent.example.com`)"
      - "traefik.http.routers.openclaw.middlewares=openclaw-https-redirect"

      # HTTPS route protected by Authentik
      - "traefik.http.routers.openclaw-secure.entrypoints=https"
      - "traefik.http.routers.openclaw-secure.rule=Host(`agent.example.com`)"
      - "traefik.http.routers.openclaw-secure.middlewares=authentik@docker"
      - "traefik.http.routers.openclaw-secure.tls=true"
      - "traefik.http.routers.openclaw-secure.tls.certresolver=cloudflare"
      - "traefik.http.routers.openclaw-secure.service=openclaw"
      - "traefik.http.services.openclaw.loadbalancer.server.port=18789"

      # Authentik outpost callback path must bypass the OpenClaw service
      - "traefik.http.routers.authentik-openclaw-outpost.entrypoints=https"
      - "traefik.http.routers.authentik-openclaw-outpost.rule=Host(`agent.example.com`) && PathPrefix(`/outpost.goauthentik.io/`)"
      - "traefik.http.routers.authentik-openclaw-outpost.priority=100"
      - "traefik.http.routers.authentik-openclaw-outpost.tls=true"
      - "traefik.http.routers.authentik-openclaw-outpost.tls.certresolver=cloudflare"
      - "traefik.http.routers.authentik-openclaw-outpost.service=authentik"
    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

networks:
  proxy:
    external: true

Replace every agent.example.com with your real hostname.

Optional: give OpenClaw access to server folders

If you want OpenClaw to help manage homelab files, you can mount a host folder.

For example:

1
2
3
4
volumes:
  - ./.openclaw:/home/node/.openclaw
  - ./.openclaw/workspace:/home/node/.openclaw/workspace
  - /home/ubuntu:/host/home/ubuntu

Inside OpenClaw, the host folder appears at:

1
/host/home/ubuntu

Be careful: this gives the agent broad file access. For safer setups, mount only specific service folders:

1
2
3
4
5
volumes:
  - ./.openclaw:/home/node/.openclaw
  - ./.openclaw/workspace:/home/node/.openclaw/workspace
  - /home/ubuntu/traefik:/host/home/ubuntu/traefik
  - /home/ubuntu/headscale:/host/home/ubuntu/headscale

Start OpenClaw

Validate Compose:

1
docker compose config

Start the gateway:

1
docker compose up -d

Check status:

1
docker compose ps

Expected result:

1
openclaw-gateway   Up ... (healthy)

Check logs:

1
docker logs -f openclaw-gateway

You should see something like:

1
2
3
[gateway] http server listening
[gateway] ready
[telegram] starting provider

Authentik setup

Create an Authentik proxy application/provider for:

1
https://agent.example.com

Recommended values:

1
2
3
4
5
Application name: OpenClaw Agent
Slug: openclaw-agent
Provider type: Proxy Provider
Mode: Forward auth, single application
External host: https://agent.example.com

Then attach that provider to your embedded Authentik outpost.

Your Traefik labels already use:

1
- "traefik.http.routers.openclaw-secure.middlewares=authentik@docker"

So the request flow becomes:

1
Browser → Traefik → Authentik → OpenClaw

Access the Web UI

Open:

1
https://agent.example.com

You should pass through Authentik first.

Then enter your OpenClaw gateway token from .env:

1
OPENCLAW_GATEWAY_TOKEN

If you see this error:

1
origin not allowed

edit this file:

1
/home/ubuntu/openclaw/.openclaw/openclaw.json

and add your public URL to:

1
2
3
4
5
6
7
"gateway": {
  "controlUi": {
    "allowedOrigins": [
      "https://agent.example.com"
    ]
  }
}

Then restart:

1
docker compose restart openclaw-gateway

Approve browser/device pairing

The first Web UI connection may ask for device pairing:

1
device pairing required (requestId: ...)

List pending devices:

1
docker exec openclaw-gateway node dist/index.js devices pending

Approve the request:

1
docker exec openclaw-gateway node dist/index.js devices approve <requestId>

Then refresh the Web UI.

Telegram setup

Create a bot with BotFather:

1
2
/start
/newbot

Put the bot token in openclaw.json:

1
2
3
4
5
6
7
"channels": {
  "telegram": {
    "enabled": true,
    "botToken": "replace_with_telegram_bot_token",
    "allowFrom": ["replace_with_your_telegram_user_id"]
  }
}

Restart:

1
docker compose restart openclaw-gateway

Send a message to your bot. If your user ID is in allowFrom, OpenClaw should reply.

Verify no direct port exposure

Run:

1
docker ps --filter name=openclaw-gateway --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}'

A secure Traefik-only setup should show no published port mapping for OpenClaw:

1
2
NAMES              PORTS     STATUS
openclaw-gateway             Up ... (healthy)

That means OpenClaw is not exposed as 18789:18789 on the host.

The only public path is:

1
Internet → Traefik :443 → Authentik → OpenClaw container port 18789

Troubleshooting

Container says config is not readable

Fix ownership and permissions:

1
2
3
4
sudo chown -R 1000:1000 /home/ubuntu/openclaw/.openclaw
sudo chmod 700 /home/ubuntu/openclaw/.openclaw
sudo chmod 600 /home/ubuntu/openclaw/.openclaw/openclaw.json
docker compose restart openclaw-gateway

Gateway token mismatch

Make sure these three values match:

  • .envOPENCLAW_GATEWAY_TOKEN
  • openclaw.jsongateway.auth.token
  • openclaw.jsongateway.remote.token

Then restart:

1
docker compose restart openclaw-gateway

Authentik redirect works but WebSocket fails

Check that your domain is included in:

1
2
3
"allowedOrigins": [
  "https://agent.example.com"
]

Also check Traefik is forwarding to the correct internal port:

1
- "traefik.http.services.openclaw.loadbalancer.server.port=18789"

Telegram does not reply

Check logs:

1
docker logs openclaw-gateway | grep -i telegram

Verify:

  • The bot token is valid
  • channels.telegram.enabled is true
  • Your Telegram user ID is in allowFrom
  • You messaged the correct bot

Security checklist

Before using this seriously:

  • Do not publish the gateway token.
  • Do not publish the Telegram bot token.
  • Keep OpenClaw behind Authentik or another trusted auth layer.
  • Do not expose port 18789 directly to the internet.
  • Mount only the host folders OpenClaw actually needs.
  • Keep .env permissions at 600.
  • Review paired devices periodically:
1
docker exec openclaw-gateway node dist/index.js devices list

Final architecture

A clean production-style deployment looks like this:

1
2
3
4
5
6
7
8
9
Cloudflare DNS
  ↓
Traefik :443
  ↓
Authentik forward-auth
  ↓
OpenClaw Gateway container
  ↓
Workspace + selected homelab folders

This gives you a practical self-hosted AI operator without opening extra ports or leaking secrets into your public documentation.

These posts connect to this topic and help build the bigger homelab picture:

This post is licensed under CC BY 4.0 by the author.