Article guideContents, topics, tags, and RSS
You can run OpenClaw on a managed Docker server even when you cannot access the host filesystem or connect over SSH. All you need is permission to deploy a Compose file, define environment variables, inspect logs, restart the service, and open a console inside the container.
This guide pulls the official prebuilt OpenClaw image from GitHub Container Registry. It does not clone the OpenClaw repository, build an image, create host directories, or run docker compose commands on the server.
Reviewed: 16 September 2026 against OpenClaw 2026.9.4. OpenClaw changes quickly, so compare this standalone file with the current OpenClaw Docker guide before a production deployment or upgrade. The configuration and commands were checked against the versioned source and documentation; this is not an end-to-end deployment test on every control panel.
The deployment model
The Compose service has two jobs:
- On its first start, it runs OpenClaw’s non-interactive onboarding command and saves the configuration in a named volume.
- After onboarding succeeds, it writes a completion marker and starts the long-running Gateway.
On later restarts or image upgrades, the marker remains in the volume, so onboarding is skipped and the Gateway starts normally.
This approach is intended for control panels such as Portainer, Dockge, Coolify, or another platform that can:
- deploy or update a Compose stack;
- inject protected environment variables;
- preserve Docker named volumes;
- show container logs and health;
- restart or redeploy a container;
- open a shell inside the running container; and
- route an HTTPS hostname or private network connection to container port
18789.
If the platform cannot persist and back up volumes, or cannot restrict Gateway access to trusted clients, resolve that before treating the deployment as production-ready. Prefer private HTTPS ingress over a VPN or an access-controlled proxy. HTTPS encrypts traffic; it does not by itself make an internet-facing operator console private.
Choose the model provider
Configure the following variables in the platform’s stack environment or secret manager. Do not paste real credentials directly into a Compose file that will be stored in source control.
Native OpenAI
| Variable | Value |
|---|---|
OPENCLAW_GATEWAY_TOKEN | A randomly generated secret of at least 32 bytes |
OPENCLAW_SETUP_PROVIDER | openai |
OPENAI_API_KEY | Your OpenAI API key |
OPENCLAW_PUBLIC_ORIGIN | The exact UI origin, such as https://openclaw.example.com |
OPENCLAW_TZ | An IANA timezone, such as Europe/Stockholm |
Generate the Gateway token with the platform’s secret generator or a password manager. It authenticates clients to OpenClaw and is separate from the model-provider key.
OPENCLAW_PUBLIC_ORIGIN must contain only the scheme, hostname, and optional port—no path or trailing slash. Leave it empty only for a channel-only deployment: the wrapper then explicitly disables the Control UI. An enabled UI on a non-loopback bind requires an allowed origin, otherwise the Gateway refuses to start.
Custom OpenAI-compatible API
For a self-hosted model server, internal proxy, or another API that implements an OpenAI protocol, keep the common Gateway, origin, and timezone variables above, then replace the native provider settings with:
| Variable | Value |
|---|---|
OPENCLAW_SETUP_PROVIDER | custom |
OPENCLAW_CUSTOM_BASE_URL | API root, such as https://llm.example.com/v1 |
OPENCLAW_CUSTOM_MODEL_ID | The exact model ID accepted by the API |
OPENCLAW_CUSTOM_PROVIDER_ID | A stable short name, such as private-openai |
OPENCLAW_CUSTOM_COMPATIBILITY | openai or openai-responses |
CUSTOM_API_KEY | The API key, when the endpoint requires one |
Use openai for an API that implements /v1/chat/completions. Use openai-responses only when it implements /v1/responses. Pass the API root as the base URL, not the complete request path.
If an intentionally private endpoint requires no authentication, leave CUSTOM_API_KEY empty. Never expose a keyless model endpoint to an untrusted network.
The base URL must be reachable from inside the OpenClaw container. Use a public or private HTTPS URL, or a Compose service name such as http://vllm:8000/v1 when the model server is part of the same stack. Do not use 127.0.0.1 for a different container or server; inside OpenClaw, it refers to the OpenClaw container itself.
Paste the Compose file
Create a new stack in the Docker control panel and use this compose.yml:
services:
openclaw-gateway:
image: ${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:2026.9.4}
environment:
HOME: /home/node
OPENCLAW_HOME: /home/node
TERM: xterm-256color
OPENCLAW_STATE_DIR: /home/node/.openclaw
OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
OPENCLAW_CONFIG_DIR: /home/node/.openclaw
OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
OPENCLAW_GATEWAY_PORT: "18789"
OPENCLAW_GATEWAY_BIND: lan
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN:?Set OPENCLAW_GATEWAY_TOKEN}
OPENCLAW_SETUP_PROVIDER: ${OPENCLAW_SETUP_PROVIDER:-openai}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
CUSTOM_API_KEY: ${CUSTOM_API_KEY:-}
OPENCLAW_CUSTOM_BASE_URL: ${OPENCLAW_CUSTOM_BASE_URL:-}
OPENCLAW_CUSTOM_MODEL_ID: ${OPENCLAW_CUSTOM_MODEL_ID:-}
OPENCLAW_CUSTOM_PROVIDER_ID: ${OPENCLAW_CUSTOM_PROVIDER_ID:-}
OPENCLAW_CUSTOM_COMPATIBILITY: ${OPENCLAW_CUSTOM_COMPATIBILITY:-openai}
OPENCLAW_PUBLIC_ORIGIN: ${OPENCLAW_PUBLIC_ORIGIN:-}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TZ: ${OPENCLAW_TZ:-UTC}
volumes:
- openclaw-state:/home/node/.openclaw
- openclaw-workspace:/home/node/.openclaw/workspace
- openclaw-auth-profiles:/home/node/.config/openclaw
cap_drop:
- NET_RAW
- NET_ADMIN
security_opt:
- no-new-privileges:true
extra_hosts:
- "host.docker.internal:host-gateway"
expose:
- "18789"
init: true
restart: unless-stopped
command:
- /bin/sh
- -eu
- -c
- |
marker=/home/node/.openclaw/.compose-onboarding-complete
if [ ! -f "$$marker" ]; then
case "$${OPENCLAW_SETUP_PROVIDER}" in
openai)
node dist/index.js onboard \
--non-interactive \
--accept-risk \
--skip-health \
--mode local \
--auth-choice openai-api-key \
--secret-input-mode ref \
--gateway-auth token \
--gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \
--skip-channels \
--no-install-daemon
;;
custom)
: "$${OPENCLAW_CUSTOM_BASE_URL:?Set OPENCLAW_CUSTOM_BASE_URL}"
: "$${OPENCLAW_CUSTOM_MODEL_ID:?Set OPENCLAW_CUSTOM_MODEL_ID}"
: "$${OPENCLAW_CUSTOM_PROVIDER_ID:?Set OPENCLAW_CUSTOM_PROVIDER_ID}"
node dist/index.js onboard \
--non-interactive \
--accept-risk \
--skip-health \
--mode local \
--auth-choice custom-api-key \
--custom-base-url "$${OPENCLAW_CUSTOM_BASE_URL}" \
--custom-model-id "$${OPENCLAW_CUSTOM_MODEL_ID}" \
--custom-provider-id "$${OPENCLAW_CUSTOM_PROVIDER_ID}" \
--custom-compatibility "$${OPENCLAW_CUSTOM_COMPATIBILITY}" \
--secret-input-mode ref \
--gateway-auth token \
--gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \
--skip-channels \
--no-install-daemon
;;
*)
echo "OPENCLAW_SETUP_PROVIDER must be openai or custom" >&2
exit 1
;;
esac
if [ -n "$${OPENCLAW_PUBLIC_ORIGIN:-}" ]; then
origins_json="$$(node -e 'process.stdout.write(JSON.stringify([process.env.OPENCLAW_PUBLIC_ORIGIN]))')"
node dist/index.js config set \
gateway.controlUi.allowedOrigins \
"$$origins_json" \
--strict-json
node dist/index.js config set gateway.controlUi.enabled true --strict-json
else
node dist/index.js config set gateway.controlUi.enabled false --strict-json
fi
if [ -n "$${TELEGRAM_BOT_TOKEN:-}" ]; then
node dist/index.js channels add \
--channel telegram \
--use-env
fi
touch "$$marker"
fi
exec node dist/index.js gateway \
--bind "$${OPENCLAW_GATEWAY_BIND}" \
--port 18789
healthcheck:
test: ["CMD", "node", "dist/docker-healthcheck.js"]
interval: 30s
timeout: 5s
retries: 5
start_period: 2m
volumes:
openclaw-state:
openclaw-workspace:
openclaw-auth-profiles:
This is a standalone adaptation of OpenClaw’s versioned official Compose definition. The image property points directly to the prebuilt release and there is no build property or source checkout.
The standard image runs as the non-root node user with UID 1000. The image pre-creates the three named-volume mount points with the required ownership, so fresh, empty Docker-managed volumes do not need host-side mkdir, chown, or chmod commands. Existing or restored volumes retain their previous ownership; if startup reports EACCES, have the platform administrator repair those volumes rather than running the Gateway as root.
OPENCLAW_SETUP_PROVIDER, OPENCLAW_CUSTOM_*, and OPENCLAW_PUBLIC_ORIGIN are inputs to the first-start wrapper in this Compose file. OPENCLAW_IMAGE is a Compose image selector. The provider keys and OPENCLAW_GATEWAY_TOKEN remain runtime environment variables because the saved OpenClaw configuration refers back to them.
The $$ sequences in the command are intentional. Compose converts them to a single $ for the shell running inside the container; without that escaping, the control panel may try to expand the shell variables while parsing the stack.
Deploy and follow the logs
Enter all required variables before deploying the stack. The first startup should show these phases in the container log:
- non-interactive onboarding;
- Control UI origin configuration, or disabling the UI for a channel-only deployment;
- optional Telegram channel configuration; and
- Gateway startup on port
18789.
The container writes .compose-onboarding-complete only after every setup step succeeds. If a required variable is absent or onboarding fails, the container exits and the restart policy retries it. Correct the stack variables and redeploy; do not create the marker manually.
Once the Gateway starts, the health check should change to healthy. OpenClaw exposes /healthz for liveness, /startupz for startup and traffic admission, and /readyz for deeper channel-aware readiness. The included probe checks liveness, not whether a model request succeeds. Docker Compose does not restart a still-running container just because it becomes unhealthy; configure monitoring or the platform’s own recovery policy separately.
Verify OpenClaw from the container console
Open the platform’s console for openclaw-gateway. The image runs as the unprivileged node user and includes the openclaw command.
Run:
openclaw doctor --json
openclaw models status
openclaw security audit
openclaw agent \
--agent main \
--message "Reply with only: OpenClaw is running"
doctor --json is read-only in this release. Inspect its ok and findings fields: generating an advisory report can exit successfully even when it reports problems. For an automated gate with failure exit codes, use openclaw doctor --lint --json. Review the security audit before admitting users.
The last command selects the initial main agent created by this guide and makes a real provider request, which may incur usage charges. If the health check succeeds but this request fails, inspect the model ID, API base URL, protocol choice, and provider credential.
Non-interactive custom-provider onboarding validates and writes the configuration but does not guarantee a live model call. Always run this small request before connecting real users.
Access the Control UI without SSH
The Compose file does not publish port 18789 on the host. expose documents the container port; it is not a firewall or an instruction that automatically connects an external proxy. The ingress proxy must share a Docker network with the service, and other containers on that network can reach the listener. Use the platform’s private or access-controlled HTTPS ingress:
- Route an HTTPS hostname to service
openclaw-gatewayon container port18789. - Enable WebSocket forwarding.
- Set
OPENCLAW_PUBLIC_ORIGINto that exact HTTPS origin before the first deployment. - Open the URL and paste
OPENCLAW_GATEWAY_TOKENinto the Control UI settings.
If the external origin changes after the first successful startup, update it from the container console and restart through the platform. To enable the UI after a channel-only deployment, set the origin first and then enable it:
openclaw config set \
gateway.controlUi.allowedOrigins \
'["https://openclaw.example.com"]' \
--strict-json
openclaw config set gateway.controlUi.enabled true --strict-json
If the platform supplies forwarded client headers, configure gateway.trustedProxies for only the ingress proxy addresses documented by that platform. Ensure the proxy overwrites forwarded client headers rather than trusting client-supplied values. Keep token authentication enabled; setting trusted proxy addresses does not require switching to trusted-proxy authentication. Do not trust every private subnet merely to make warnings disappear.
If there is no managed ingress, a platform administrator can bind a host port to a specific private interface. Set OPENCLAW_PRIVATE_BIND_IP to an address actually assigned to the Docker host (not 0.0.0.0), then replace the expose block with:
ports:
- "${OPENCLAW_PRIVATE_BIND_IP:?Set the Docker host private IP}:18789:18789"
The shorter 18789:18789 mapping binds to all host interfaces, not just the private network. A private-IP binding still needs firewall and routing controls; Docker-published ports can bypass ordinary UFW rules. See Docker port publishing and Docker packet filtering.
Set OPENCLAW_PUBLIC_ORIGIN to the exact URL used by the client. Prefer HTTPS even on a private network: plain HTTP exposes the Gateway token and conversation traffic to on-path observers. In the reviewed version, browser device pairing can work over HTTP, but secure-context features such as passkeys still need HTTPS. Never expose this plaintext port directly to the internet.
When the Control UI asks for device approval, use the container console:
openclaw devices list
openclaw devices approve <requestId>
Add Telegram
If TELEGRAM_BOT_TOKEN exists during the first deployment, the bootstrap command adds the default Telegram account automatically. --use-env supports that default account; use the Telegram account configuration for additional accounts. Direct messages use pairing by default. Send the bot a message, then approve its code from the container console:
openclaw pairing list telegram
openclaw pairing approve telegram <code>
To add Telegram later:
- Add
TELEGRAM_BOT_TOKENto the platform’s protected environment. - Redeploy so the running container receives the new variable.
- Open its console and run:
openclaw channels add --channel telegram --use-env
The official Telegram guide covers group policies, allowlists, and additional channel settings.
Optionally expose an OpenAI-compatible API
The custom-provider settings above tell OpenClaw which upstream model API to call. The reverse integration is different: another application can call an OpenClaw agent through POST /v1/chat/completions.
The endpoint is disabled by default. Keep it restricted to a VPN, tailnet, or private ingress; do not expose it to the public internet, even behind HTTPS and a bearer token. Enable it from the container console:
openclaw config set \
gateway.http.endpoints.chatCompletions.enabled \
true \
--strict-json
Restart the container from the platform, then call the endpoint from your workstation or API client through your private HTTPS origin:
curl -sS https://openclaw.example.com/v1/chat/completions \
-H "Authorization: Bearer <gateway-token>" \
-H "Content-Type: application/json" \
-d '{
"model": "openclaw/default",
"messages": [
{"role": "user", "content": "Reply with only: API is working"}
]
}'
Here, openclaw/default follows the configured default agent rather than naming an agent literally called default. The model field selects an OpenClaw agent rather than the raw upstream model. The agent then uses the native OpenAI or custom provider configured during onboarding.
Treat the bearer token as full operator access. Requests use the agent’s normal tools and permissions, so this is not a restricted model-only API key. The Chat Completions documentation describes its fields, routing, and security boundary.
Operate the deployment from the control panel
| Task | Action |
|---|---|
| Inspect startup or errors | Open the openclaw-gateway container logs |
| Run an OpenClaw command | Open the container console and run openclaw <command> |
| Apply changed environment variables | Redeploy or recreate the service; a simple restart retains the old environment |
| Restart after a config change | Use the platform’s restart action |
| Upgrade OpenClaw | Change OPENCLAW_IMAGE to a tested exact version and redeploy |
| Preserve data | Retain and back up all three named volumes |
The onboarding marker prevents ordinary restarts and image upgrades from overwriting the initial configuration. Changing provider variables later does not automatically rewrite the saved provider configuration. Run the appropriate onboarding or configuration command deliberately from the console when changing providers.
Back up before upgrading
The container is disposable; the named volumes are not:
| Volume | Contents |
|---|---|
openclaw-state | Gateway configuration, databases, credentials, plugins, and other state |
openclaw-workspace | Agent workspace and generated artifacts |
openclaw-auth-profiles | Auth-profile recovery key material |
Use the platform’s volume snapshot, export, or backup feature and verify that it can restore all three volumes together. Stop the Gateway first when the backup mechanism does not provide an application-consistent snapshot. Copying a live SQLite database is not a reliable backup strategy.
Do not select remove volumes, delete persistent data, or an equivalent option during a redeploy. Keep the platform’s stack/project name and the three volume declarations unchanged so Compose continues to attach the same volumes.
To upgrade:
- Take and verify a backup.
- Review the newer OpenClaw release and Docker notes.
- Change
OPENCLAW_IMAGEto a new exact tag, such asghcr.io/openclaw/openclaw:<version>. - Ask the platform to pull the image and redeploy the service.
- Check health, logs,
openclaw doctor --json, and a small model request.
Use an exact release tag rather than a moving channel such as latest. For a reproducible artifact, pin a verified registry digest (ghcr.io/openclaw/openclaw@sha256:<digest>) when the platform supports it. A version-looking tag alone is not a cryptographic guarantee, and a digest pin must still be deliberately updated to receive fixes.
Understand the security boundary
Running the Gateway in Docker does not automatically enable OpenClaw’s agent sandbox. Agent tools can still read and change everything mounted inside the container, including the workspace. The standard prebuilt image also does not need the host Docker socket for this deployment; do not add that mount casually.
Anyone who administers the Docker host can inspect container environment variables and volumes. A managed platform therefore remains part of the trust boundary even when its UI labels values as secrets.
Before connecting untrusted senders or enabling powerful tools, read the official Gateway security and sandboxing guides. Keep direct messages in pairing or allowlist mode, approve browser devices deliberately, and expose the smallest possible workspace.
Treat one Gateway as one trust boundary for a single operator or a mutually trusting team. Separate mutually untrusted users into separate Gateways and platform projects rather than relying on prompts for tenant isolation. Each stack must have a distinct project name so its Compose-managed volumes remain separate.
With those constraints in place, the deployment is fully manageable from a Docker control panel: Compose pulls the prebuilt image, the first container start performs unattended onboarding, named volumes preserve the state, and every later administrative command runs through the container console.
