---
title: "Deploy OpenClaw Desktop (GUI)"
description: "OpenClaw on an Ubuntu desktop: watch its Chrome, sign in to sites for it"
category: "AI/ML"
url: https://railway.com/deploy/openclaw-desktop-gui
---

# Deploy OpenClaw Desktop (GUI)

OpenClaw on an Ubuntu desktop: watch its Chrome, sign in to sites for it

**[Deploy OpenClaw Desktop (GUI) on Railway](https://railway.com/template/openclaw-desktop-gui)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/openclaw-desktop-gui/manifest.json

- **Creator:** darseen
- **Category:** AI/ML

## Template content

### proxy https://github.com/caddyserver.png?size=460

- **Image:** caddy:2-alpine
- **Start command:** `sh -c 'printf "%s" "$CADDYFILE" > /etc/caddy/Caddyfile && exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile'`
- **Health check:** /proxy-healthz
- **Public domain:** Yes

### desktop https://openclaw.ai/favicon.svg

- **Image:** kasmweb/ubuntu-noble-desktop:1.19.0-rolling-weekly
- **Start command:** `bash -c 'set -e
for k in ANTHROPIC_API_KEY OPENAI_API_KEY OPENROUTER_API_KEY GEMINI_API_KEY TELEGRAM_BOT_TOKEN DISCORD_BOT_TOKEN; do eval v=\$$k; [ -n "$v" ] || unset $k; done
if [ ${#VNC_PW} -lt 6 ]; then echo "FATAL: VNC_PW must be at least 6 characters (KasmVNC requirement). Set a longer password and redeploy." >&2; exit 1; fi
if [ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]; then echo "FATAL: OPENCLAW_GATEWAY_TOKEN is empty. Set one and redeploy." >&2; exit 1; fi
echo "kasm-user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/kasm-user
chmod 0440 /etc/sudoers.d/kasm-user
chown 1000:1000 /home/kasm-user
if [ ! -f /home/kasm-user/.railway-owned ]; then chown -R 1000:1000 /home/kasm-user; touch /home/kasm-user/.railway-owned; chown 1000:1000 /home/kasm-user/.railway-owned; fi
sed -i "s|function wait_for_network_devices() {|function wait_for_network_devices() { return 0;|" /dockerstartup/vnc_startup.sh
grep -q "wait_for_network_devices() { return 0;" /dockerstartup/vnc_startup.sh || { echo FATAL: Kasm startup changed, wait_for_network_devices patch no longer applies >&2; exit 1; }
for b in node npm npx openclaw; do ln -sf /home/kasm-user/.local/node/bin/$b /usr/local/bin/$b; done
cat > /dockerstartup/provision.sh <<"PROVEOF"
set -u
export HOME=/home/kasm-user
cd $HOME
for i in $(seq 1 60); do [ -f $HOME/.bashrc ] && break; sleep 1; done
log() { echo "[provision] $*"; }
NODE_DIR=$HOME/.local/node
if [ ! -x $NODE_DIR/bin/node ]; then
  log installing Node.js 24 LTS
  TARBALL=$(curl -fsSL https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt | grep -o "node-v[0-9.]*-linux-x64.tar.xz" | head -1)
  mkdir -p $NODE_DIR
  curl -fsSL "https://nodejs.org/dist/latest-v24.x/$TARBALL" | tar -xJ -C $NODE_DIR --strip-components=1 || log Node.js install failed
fi
export PATH=$HOME/.local/bin:$HOME/.local/node/bin:$PATH
export npm_config_cache=/tmp/npm-cache
if [ ! -x $NODE_DIR/bin/openclaw ]; then
  log installing OpenClaw
  npm install -g openclaw@latest >/tmp/npm-openclaw.log 2>&1 || log OpenClaw install failed, see /tmp/npm-openclaw.log
fi
grep -q "local/node/bin" $HOME/.bashrc || echo "export PATH=\$HOME/.local/bin:\$HOME/.local/node/bin:\$PATH" >> $HOME/.bashrc
OC=$HOME/.openclaw
mkdir -p $OC $HOME/.local/bin $HOME/.local/share/icons $HOME/Desktop
export DISPLAY=:1
if [ ! -f $OC/openclaw.json ]; then
  AUTH="--auth-choice skip"
  if [ -n "${ANTHROPIC_API_KEY:-}" ]; then AUTH="--auth-choice apiKey --anthropic-api-key $ANTHROPIC_API_KEY"
  elif [ -n "${OPENAI_API_KEY:-}" ]; then openclaw plugins install codex --accept-capabilities > $OC/plugin-codex.log 2>&1 || log Codex plugin install failed; AUTH="--auth-choice openai-api-key --openai-api-key $OPENAI_API_KEY"
  elif [ -n "${OPENROUTER_API_KEY:-}" ]; then AUTH="--auth-choice openrouter-api-key --openrouter-api-key $OPENROUTER_API_KEY"
  elif [ -n "${GEMINI_API_KEY:-}" ]; then AUTH="--auth-choice gemini-api-key --gemini-api-key $GEMINI_API_KEY"
  fi
  log running first-time onboarding
  openclaw onboard --non-interactive --accept-risk --skip-health --mode local --no-install-daemon --workspace $OC/workspace --gateway-bind loopback --gateway-port 18789 --gateway-auth token --gateway-token "$OPENCLAW_GATEWAY_TOKEN" --flow quickstart --json $AUTH > $OC/onboard.log 2>&1 || log onboarding failed, see ~/.openclaw/onboard.log
fi
openclaw config set --json browser.enabled true >/dev/null 2>&1
openclaw config set --json browser.headless false >/dev/null 2>&1
openclaw config set --json browser.noSandbox true >/dev/null 2>&1
openclaw config set browser.executablePath /opt/google/chrome/chrome >/dev/null 2>&1
openclaw config set --json browser.extraArgs "[\"--disable-dev-shm-usage\"]" >/dev/null 2>&1
openclaw config set gateway.auth.token "$OPENCLAW_GATEWAY_TOKEN" >/dev/null 2>&1
if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then openclaw config set --json channels.telegram "{\"enabled\":true,\"dmPolicy\":\"pairing\",\"botToken\":\"$TELEGRAM_BOT_TOKEN\",\"groupPolicy\":\"open\"}" >/dev/null 2>&1 || log could not configure Telegram; fi
if [ -n "${DISCORD_BOT_TOKEN:-}" ]; then openclaw config set --json channels.discord "{\"enabled\":true,\"token\":\"$DISCORD_BOT_TOKEN\",\"groupPolicy\":\"open\",\"dm\":{\"policy\":\"pairing\"}}" >/dev/null 2>&1 || log could not configure Discord; fi
geticon() { [ -s $HOME/.local/share/icons/$1.svg ] || curl -fsSL "$2" -o $HOME/.local/share/icons/$1.svg || rm -f $HOME/.local/share/icons/$1.svg; }
geticon openclaw https://openclaw.ai/favicon.svg
printf "#!/bin/bash\nexec google-chrome http://127.0.0.1:18789/#token=%s\n" "$OPENCLAW_GATEWAY_TOKEN" > $HOME/.local/bin/openclaw-ui
cat > $HOME/.local/bin/openclaw-browser <<"BREOF"
#!/bin/bash
export PATH=$HOME/.local/bin:$HOME/.local/node/bin:$PATH
echo Starting the agent browser, the window opens on this desktop.
openclaw browser --browser-profile openclaw start || { echo; echo Could not start it. Is the gateway running? Check OpenClaw Logs.; sleep 8; }
BREOF
cat > $HOME/.local/bin/openclaw-shell <<"SHEOF"
#!/bin/bash
echo "OpenClaw CLI. Useful commands:"
echo "  openclaw onboard                                    set up the model provider; OAuth logins open in Chrome here"
echo "  openclaw status                                     gateway, model and channels"
echo "  openclaw channels add                               connect Telegram, Discord, WhatsApp, Slack and more"
echo "  openclaw browser --browser-profile openclaw start   open the agent browser to sign in to sites for it"
echo "  openclaw logs --follow                              gateway log"
echo
exec bash -l
SHEOF
cat > $HOME/.local/bin/openclaw-logs <<"LGEOF"
#!/bin/bash
exec tail -n 200 -f $HOME/.openclaw/gateway.out
LGEOF
chmod 755 $HOME/.local/bin/openclaw-ui $HOME/.local/bin/openclaw-browser $HOME/.local/bin/openclaw-shell $HOME/.local/bin/openclaw-logs
launcher() { f=$HOME/Desktop/$1.desktop; [ -f $f ] && return 0; icon=utilities-terminal; [ -s $HOME/.local/share/icons/$4.svg ] && icon=$HOME/.local/share/icons/$4.svg
  printf "[Desktop Entry]\nType=Application\nName=%s\nExec=%s\nPath=$HOME\nIcon=%s\nTerminal=false\nCategories=Network;\n" "$2" "$3" "$icon" > $f; chmod +x $f; }
launcher openclaw-ui "OpenClaw UI" "$HOME/.local/bin/openclaw-ui" openclaw
launcher openclaw-browser "Agent Chrome" "xfce4-terminal --title=Agent-Chrome --command=$HOME/.local/bin/openclaw-browser" openclaw
launcher openclaw-terminal "OpenClaw CLI" "xfce4-terminal --title=OpenClaw --command=$HOME/.local/bin/openclaw-shell" none
launcher openclaw-logs "Gateway Log" "xfce4-terminal --title=OpenClaw-Logs --command=$HOME/.local/bin/openclaw-logs" none
TIDIED=0
if [ ! -d "$HOME/Desktop/More apps" ]; then TIDIED=1; mkdir -p "$HOME/Desktop/More apps"; for f in slack gimp thunderbird signal-desktop firefox telegram org.remmina.Remmina sublime_text onlyoffice-desktopeditors Zoom nextcloud; do [ -f $HOME/Desktop/$f.desktop ] && mv $HOME/Desktop/$f.desktop "$HOME/Desktop/More apps/"; done; fi
for i in $(seq 1 120); do SESS=$(pgrep -u 1000 -x xfce4-session | head -1); [ -n "$SESS" ] && break; sleep 1; done
if [ -n "$SESS" ]; then
  export $(tr "\0" "\n" < /proc/$SESS/environ | grep -E "^(DBUS_SESSION_BUS_ADDRESS|SESSION_MANAGER|XDG_RUNTIME_DIR)=")
  for f in openclaw-ui openclaw-browser openclaw-terminal openclaw-logs; do gio set -t string $HOME/Desktop/$f.desktop metadata::xfce-exe-checksum "$(sha256sum $HOME/Desktop/$f.desktop | cut -d" " -f1)" || log could not mark $f trusted; done
  [ "$TIDIED" = 1 ] && { sleep 2; xfdesktop --arrange || log could not arrange desktop icons; }
fi
log setup done, starting the gateway
while true; do
  openclaw gateway run --bind loopback --port 18789 --auth token --token "$OPENCLAW_GATEWAY_TOKEN" --allow-unconfigured >> $OC/gateway.out 2>&1
  log gateway exited with status $?, restarting in 5s
  sleep 5
done
PROVEOF
chmod 755 /dockerstartup/provision.sh
setpriv --reuid=1000 --regid=1000 --init-groups bash -c "bash /dockerstartup/provision.sh > /home/kasm-user/.provision.log 2>&1" &
exec setpriv --reuid=1000 --regid=1000 --init-groups /dockerstartup/kasm_default_profile.sh /dockerstartup/vnc_startup.sh /dockerstartup/kasm_startup.sh --tail-log'`

## Documentation

# Deploy and Host OpenClaw Desktop on Railway

![OpenClaw Desktop: the OpenClaw Control UI and the agent's own Chrome window side by side on the Ubuntu desktop](https://vaze.up.railway.app/api/hosting/railway-templates/openclaw-desktop-gui/openclaw-desktop.png)

![The desktop on first boot, with launchers for the OpenClaw UI, the agent's Chrome, the CLI and the gateway log](https://vaze.up.railway.app/api/hosting/railway-templates/openclaw-desktop-gui/openclaw-desktop-launchers.png)

OpenClaw Desktop runs the OpenClaw personal AI assistant on an Ubuntu 24.04
XFCE desktop that lives in your browser tab. The agent's Chrome is a window on
that desktop, so you can watch it browse, take over when a site wants a login
or a captcha, and leave it signed in for next time. The Control UI, the CLI and
the gateway log are one double-click away, and Telegram, Discord and the other
channels work as they do on any OpenClaw install.

## About Hosting OpenClaw Desktop

Deploying runs two services: the desktop, and a small Caddy proxy that is the
only public surface. The desktop image is Kasm's Ubuntu Noble workspace (about
2.6 GB compressed), which ships Chrome. On first boot a background job installs
Node.js 24 and OpenClaw into the home directory, which is a Railway volume,
runs OpenClaw's non-interactive onboarding with whichever model key you set,
and starts the gateway with the desktop's display attached. The login page
appears within a minute or two and OpenClaw is up about a minute later; later
boots skip the download. The gateway listens on loopback only and is restarted
by the same job if it ever exits.

Plan on 3 to 4 GB of RAM: the desktop idles near 1 GB, the gateway takes about
0.5 GB, and each Chrome window (the agent's and yours) adds a few hundred MB.
Leave app sleeping off; it would stop the gateway and its channels.

## Common Use Cases

- An OpenClaw assistant on Telegram or Discord that browses with a real Chrome you can watch, instead of a headless one you debug from screenshots
- Signing in to your accounts in the agent's browser once, so it can act inside them afterwards
- Connecting model providers through the Control UI or `openclaw onboard`, including the subscription OAuth logins that need a browser on the gateway host

## Dependencies for OpenClaw Desktop Hosting

- A Railway volume mounted at `/home/kasm-user`, which holds OpenClaw, its config, sessions and the agent browser's profile. Without it, everything is lost on redeploy.
- A model provider. Set `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY` or `GEMINI_API_KEY` at deploy time, or leave them all blank and pick a provider from the desktop after the first boot (Model Setup in the Control UI, or `openclaw onboard` in the OpenClaw CLI launcher).
- Optional: `TELEGRAM_BOT_TOKEN` or `DISCORD_BOT_TOKEN` to connect a channel at boot. Other channels are added from the desktop with `openclaw channels add`.
- No GitHub repo backs this template. Both services boot from public images.

### Deployment Dependencies

- [OpenClaw](https://openclaw.ai/) - installed from npm (`openclaw@latest`) on first boot
- [OpenClaw browser docs](https://docs.openclaw.ai/tools/browser) - what the agent can do with its Chrome
- [OpenClaw channel docs](https://docs.openclaw.ai/channels) - Telegram, Discord, WhatsApp, Slack and the rest
- [kasmweb/ubuntu-noble-desktop](https://hub.docker.com/r/kasmweb/ubuntu-noble-desktop) - the upstream desktop image
- [KasmVNC](https://github.com/kasmtech/KasmVNC) - the streaming layer
- [Caddy](https://caddyserver.com/) - the TLS-terminating proxy
- [Node.js](https://nodejs.org/) - 24 LTS, installed as a tarball into the home directory

### Implementation Details

**What is on the desktop.** Four launchers. *OpenClaw UI* opens the Control UI
in Chrome at `http://127.0.0.1:18789` with the gateway token already in the
URL. *Agent Chrome* starts the agent's own browser profile so you can sign in
to sites for it. *OpenClaw CLI* is a terminal with the useful commands listed.
*Gateway Log* tails the gateway output. Kasm's other apps are in the *More
apps* folder and in the Applications menu.

**Why the gateway has no public URL.** Since OpenClaw 2026.8.1 a browser that
reaches the Control UI over the network needs a one-time device approval, while
a loopback connection is approved automatically once the token matches. Opening
the UI inside the desktop is loopback, so there is nothing to pair, and the
channels connect outbound, so the gateway needs no domain. It binds to
127.0.0.1 and is reachable only from the desktop.

**How the agent's browser ends up on screen.** The gateway runs as the desktop
user with `DISPLAY=:1`, and the template sets `browser.headless: false`,
`browser.noSandbox: true` (a container without user namespaces),
`browser.executablePath` to the image's Chrome and `--disable-dev-shm-usage`
for Railway's 64 MB `/dev/shm`. When the agent uses its browser tool, or when
you click *Agent Chrome*, OpenClaw launches its managed `openclaw` profile
(orange accent) as a normal window. The profile lives in
`~/.openclaw/browser/openclaw` on the volume, so logins persist across
redeploys.

**Onboarding.** On first boot the template runs `openclaw onboard
--non-interactive` with the first key it finds: Anthropic, OpenAI, OpenRouter,
Gemini, in that order (OpenAI first installs OpenClaw's Codex runtime plugin).
With no key it onboards with `--auth-choice skip`, and the OpenClaw UI launcher
opens on the Model Setup page, where every provider, including the subscription
OAuth logins, can be connected from the desktop's browser. Onboarding runs
once; the browser settings, gateway token and channel tokens are re-applied on
every boot, so a changed variable takes effect after a redeploy. Output is in
`~/.openclaw/onboard.log`, provisioning progress in `~/.provision.log`.

**Channels.** `TELEGRAM_BOT_TOKEN` and `DISCORD_BOT_TOKEN` are written into
`channels.telegram` and `channels.discord` with DM pairing, OpenClaw's default:
the first message from a new user returns a pairing code, which you approve
from the Control UI or with `openclaw pairing approve telegram <code>`.
WhatsApp is QR-only: run `openclaw channels login --channel whatsapp` in the
OpenClaw CLI launcher and scan the code on screen, which is what the desktop is
for.

**`VNC_PW` has a 6-character minimum.** KasmVNC's password tool silently
rejects anything shorter, which would leave the desktop unable to start and the
URL serving 502. The template validates the length at boot and fails with an
explicit message instead.

**Passwordless sudo is enabled deliberately.** Kasm ships `kasm-user` with a
locked password and no sudo rights, so a stock container cannot `apt install`
anything. The template grants passwordless sudo at boot. To lock it back down,
remove `/etc/sudoers.d/kasm-user` inside the desktop.

**Security model.** `VNC_PW` is the only thing between the public Railway URL
and a root-capable desktop holding your OpenClaw config, model keys, channel
tokens and the agent browser's logins. Set a strong one. The gateway token is
generated per deployment and never leaves the desktop.

## Why Deploy OpenClaw Desktop on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying OpenClaw Desktop on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.
</code>

## Similar templates

- [Chat Chat](https://railway.com/deploy/-WWW5r) — Chat Chat, your own unified chat and search to AI platform.
- [stella](https://railway.com/deploy/stella) — Self-host stella with web, API, Postgres, Redis, and object storage.
- [Hermes Agent | OpenClaw Alternative with Dashboard](https://railway.com/deploy/hermes-agent-or-openclaw-alternative-wit) — Self-Hosted Hermes AI Agent for Telegram, Discord & Slack

Open this page in a browser: https://railway.com/deploy/openclaw-desktop-gui
