Skip to content

openclaw-desktop

Recipe card from the charly-openclaw plugin (Images — the deployable catalog).

The all-in-one workstation box: a browser-accessible Wayland streaming desktop that also runs the OpenClaw gateway + its full tool/skill stack, a CPU Ollama inference server, and the complete charly toolchain — all as uid 1000 user with no --privileged and no added capabilities. Open https://localhost:3000, get a labwc desktop with Chrome, and from a terminal inside it you can charly box build, run nested rootless pods, launch rootless libvirt VMs, drive the OpenClaw gateway on :18789, and hit a local Ollama on :11434.

openclaw-desktop:
base: cachyos.cachyos # Arch-derived, pacman/AUR, x86_64_v3 (via the `cachyos` import namespace)
build: [pac, aur] # required — selkies' chrome (AUR google-chrome) + wl-tools (AUR wlrctl)
candy:
- agent-forwarding
- selkies-desktop # full streaming desktop stack
- openclaw-full # gateway + 27 tools incl. claude-code/codex/gemini
- ollama # CPU ollama (GPU-agnostic layer)
- charly # the full toolchain: charly binary + virtualization + gocryptfs + socat
- container-nesting # nested rootless podman/buildah/skopeo
- golang
- gh
- dbus
- charly
port:
- "3000:3000" # Selkies web UI (Traefik HTTPS)
- "9222:9222" # Chrome DevTools Protocol (cdp-proxy)
- "9224:9224" # chrome-devtools-mcp (Streamable HTTP)
- "2222:2222" # sshd-wrapper
- "18789:18789" # OpenClaw gateway
- "11434:11434" # Ollama API
platform: [linux/amd64]

No uid:/gid:/user:/network: override — inherits 1000/1000/user and the default charly bridge network. Rootless is the whole design.

base: cachyos.cachyos (/charly-distros:cachyos, reached via the cachyos import namespace) is the Arch-derived x86_64_v3 base. The box is CPU-only — there is no nvidia/cuda candy. Ollama auto-detects the absence of a GPU and runs CPU inference; selkies streams via the CPU x264 encoder. build: [pac, aur] is mandatory (not inherited): selkies’ chrome candy compiles google-chrome from AUR and wl-tools builds wlrctl; inheriting cachyos’s bare [pac] would gate out the AUR builder and silently drop the browser. The aur → arch-builder builder map comes from the cachyos base.

Field Value
cap_add (empty)
security_opt [unmask=/proc/*] (from /charly-distros:container-nesting)
devices [/dev/fuse, /dev/net/tun] (from /charly-distros:container-nesting)
shm_size 1g (from /charly-selkies:chrome)
memory_max 6g (from /charly-selkies:chrome)
privileged false
Network default charly bridge (NOT host)
UID / user 1000 / user

No --privileged. No cap_add: ALL. No seccomp=unconfined. No label=disable. The only security relaxation is unmask=/proc/* — surgical, documented in /charly-distros:container-nesting with the full kernel-level RCA.

Stack Candies What it gives you
Streaming desktop selkies-desktop (chrome, chrome-cdp, labwc, waybar, pipewire, swaync, pavucontrol, wl-tools, selkies, sshd, …) labwc Wayland desktop streamed over HTTPS:3000; Chrome + CDP:9222 + chrome-devtools-mcp:9224; sshd:2222
OpenClaw + tools openclaw-full (openclaw gateway + claude-code, codex, gemini + 24 more tools) AI gateway on :18789; claude CLI at /usr/local/bin/claude, codex / gemini CLIs at ${HOME}/.npm-global/bin/; playwright now drives the desktop’s real Chrome (synergy)
LLM inference ollama CPU Ollama API on :11434; ollama host alias; models volume at ~/.ollama
Nested charly toolchain charly + container-nesting + golang + gh charly box build, nested rootless podman/buildah/skopeo, rootless libvirt VMs, gocryptfs encrypted volumes, socat relays

Browser synergy: openclaw-full ships playwright but deliberately omits a system browser (it’s normally headless). Fusing it with selkies-desktop’s chrome + chrome-cdp stack gives playwright a real browser + CDP on :9222 — a pure enhancement, no conflict.

Port Service
3000 Selkies web UI (Traefik HTTPS, self-signed)
9222 Chrome DevTools Protocol (via cdp-proxy)
9224 chrome-devtools-mcp (Streamable HTTP)
2222 sshd-wrapper (supervisord-managed)
18789 OpenClaw gateway + Control UI
11434 Ollama API

All six are distinct — no collisions. For multi-instance deployments alongside a running selkies/openclaw instance holding these host ports, remap via charly config openclaw-desktop -p 3010:3000 -p 9232:9222 -p 9242:9224 -p 2232:2222 -p 18790:18789 -p 11444:11434.

Terminal window
charly box build openclaw-desktop
charly config openclaw-desktop
charly start openclaw-desktop
# Desktop: https://localhost:3000 (accept the self-signed cert)
# Gateway: http://localhost:18789
# Ollama: curl http://localhost:11434/api/tags
charly shell openclaw-desktop -c "ollama pull llama3"

Nested rootless podman — how it works here

Section titled “Nested rootless podman — how it works here”

See /charly-distros:container-nesting for the full kernel-level RCA. The short version: podman’s rootless outer container injects linux.maskedPaths; the kernel’s mount_too_revealing() then refuses any fresh /proc mount from the inner container. unmask=/proc/* tells podman not to emit those masks on the outer, so the inner /proc mount has nothing to mismatch with. Verified:

Terminal window
charly shell openclaw-desktop -c 'podman run --rm quay.io/libpod/alpine:latest /bin/true'
# zero extra caps, uid 1000

See /charly-infrastructure:virtualization. virtqemud --timeout 0 + virtnetworkd --timeout 0 run as supervisord programs at uid 1000; libvirt qemu:///session keys off $XDG_RUNTIME_DIR and needs no CAP_SYS_ADMIN — only /dev/kvm passthrough.

Terminal window
charly shell openclaw-desktop -c 'virsh -c qemu:///session list --all'

Two-level nested-virtualization (end-to-end charly vm run-through)

Section titled “Two-level nested-virtualization (end-to-end charly vm run-through)”

The full charly vm build/create/ssh/stop/destroy lifecycle completes inside the rootless pod, two levels of KVM nesting deep:

  1. Host (rootless podman, uid 1000) runs charly-openclaw-desktop.
  2. Inside it, charly vm build <bootc-image> --transport containers-storage auto-falls back to engine.rootful=machine (no host sudo in reach), which spawns a podman-machine VM (nested VM #1) via KVM passthrough and runs bootc install to-disk.
  3. charly vm create <bootc-image> -i smoke --ssh-key generate boots the produced qcow2 as a QEMU user-net VM (nested VM #2).
  4. charly vm ssh <bootc-image> -i smoke -- uname -a returns the guest kernel.
  5. charly vm destroy --disk cleans up.

No --privileged, no cap_add, no seccomp relaxations beyond the baked [unmask=/proc/*]. Only /dev/kvm passthrough.

Cross-storage loading for private bootc images

Section titled “Cross-storage loading for private bootc images”

The podman-machine in step 2 has its own rootful storage, separate from the outer container’s nested rootless store. Load a private bootc image via the host:

Terminal window
# on host
podman save -o /tmp/bootc.tar ghcr.io/<registry>/<image>:latest
charly cp openclaw-desktop /tmp/bootc.tar :/tmp/bootc.tar
# inside the pod
podman load -i /tmp/bootc.tar # nested rootless store
podman --connection charly-root load -i /tmp/bootc.tar # podman-machine rootful store
charly vm build <image> --transport containers-storage

For nested test harnesses, prefer quay.io/libpod/alpine:latest (unmetered) over docker.io/library/alpine — the baked nested-podman-run check already uses this mirror.

Every charly verb family runs as uid 1000 inside the container sandbox: charly box build/generate/validate/merge/inspect/list/pull, charly check box/live/wl/libvirt (cdp/vnc/mcp/record/kube/adb/appium/spice/dbus are declarative out-of-process verbs), charly config/deploy/start/stop/update/remove/shell/cmd/service/status/logs, charly vm list/create/start/stop/ssh/destroy (rootless libvirt session), charly doctor/secrets/settings/alias. The charly candy bakes only the binary — for build-mode verbs that read charly.yml, mount or charly cp the project in.

  • chrome-data~/.chrome-debug (Chrome profile, from the chrome candy)
  • selkies-config~/.config/selkies
  • models~/.ollama (Ollama model storage)
Terminal window
charly status openclaw-desktop # all services RUNNING
curl -k https://localhost:3000 # selkies HTTPS 200
curl -s http://localhost:18789 # openclaw gateway
curl -s http://localhost:11434/api/tags # ollama API
charly shell openclaw-desktop -c 'podman run --rm quay.io/libpod/alpine:latest /bin/true'

A desktop screenshot is captured by a wl: screenshot step, run with charly check live openclaw-desktop --filter wl.

The baked box-level check: carries the nested-rootless posture checks (subuid two-ranges, newuidmap cap, policy.json, containers.conf userns=host, _CONTAINERS_USERNS_CONFIGURED + BUILDAH_ISOLATION env), the deploy-scope nested-toolchain checks (nested podman run, virsh session list, in-container charly version/charly doctor), and the three fused services’ liveness (gateway, ollama API, chrome-devtools-mcp port). The R10 bed is check-openclaw-desktop-pod (charly check run check-openclaw-desktop-pod).

MUST be invoked when the task involves:

  • Building, deploying, or troubleshooting the openclaw-desktop box.
  • Running charly verbs (image build, nested pods, rootless VMs) from inside a streaming desktop.
  • The OpenClaw gateway, AI CLIs, or a CPU Ollama running alongside a Wayland streaming desktop in one box.
  • The non---privileged rootless-in-rootless nesting path on a CachyOS desktop.