Connect to an Omarchy Fleet desktop with noVNC
Add browser-based desktop access to an Omarchy VM running on Cua Fleet.
Use noVNC to view and control an Omarchy Fleet desktop from an authenticated browser. This guide connects four components:
Browser -> Fleet /api/svc proxy -> websockify :5900 -> WayVNC :5902 -> HyprlandFleet exposes the HTTP and WebSocket endpoint through a service named vnc.
It does not expose the guest's raw VNC port to the internet.
Before you start#
You need:
- an Omarchy pool definition that you can update;
- an active Fleet claim with shell access to its guest; and
- a browser that can sign in to Cua.
Follow Run Omarchy on Fleet to create the pool and claim.
Add the Fleet service#
Add a service named vnc on guest port 5900 to the pool definition:
pool = await Pool.apply(
image,
name=POOL_NAME,
replicas=1,
cpu=4,
memory_mb=6144,
services={"server": 8000, "mcp": 3000, "vnc": 5900},
ttl_seconds_after_created=21600,
)Apply the pool definition, then create a claim. If you add the service while a
sandbox is already bound, create a new claim if the current sandbox does not
show vnc in its service list.
Do not expose port 5902 as another Fleet service. WayVNC uses that raw RFB port inside the
guest. Only the noVNC bridge on port 5900 needs a Fleet service.
Install noVNC and websockify#
Open a shell in the claimed Omarchy guest. Install noVNC 1.7.0 and
websockify 0.13.0 under your user account:
NOVNC_VERSION="1.7.0"
WEBSOCKIFY_VERSION="0.13.0"
VNC_ROOT="$HOME/.local/share/omarchy-vnc"
NOVNC_ARCHIVE="$VNC_ROOT/noVNC-v${NOVNC_VERSION}.tar.gz"
WEBSOCKIFY_VENV="$VNC_ROOT/websockify-venv"
install -d "$VNC_ROOT"
curl --fail --location \
"https://github.com/novnc/noVNC/archive/refs/tags/v${NOVNC_VERSION}.tar.gz" \
--output "$NOVNC_ARCHIVE"
tar --extract --gzip --file "$NOVNC_ARCHIVE" --directory "$VNC_ROOT"
python3 -m venv "$WEBSOCKIFY_VENV"
"$WEBSOCKIFY_VENV/bin/python" -m pip install \
"websockify==${WEBSOCKIFY_VERSION}"The Omarchy Fleet image starts WayVNC with the Hyprland graphical session. The
verified setup moves the raw RFB listener to loopback port 5902 and leaves
port 5900 to the Fleet-facing bridge:
install -d "$HOME/.config/systemd/user/wayvnc.service.d"
tee "$HOME/.config/systemd/user/wayvnc.service.d/override.conf" >/dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/wayvnc 127.0.0.1 5902
EOF
systemctl --user daemon-reload
systemctl --user restart wayvnc.servicewebsockify serves the noVNC files on port 5900 and bridges WebSocket traffic
to the WayVNC listener on 5902.
Run websockify as a user service#
Create a systemd user service:
install -d "$HOME/.config/systemd/user"
tee "$HOME/.config/systemd/user/omarchy-websockify.service" >/dev/null <<'EOF'
[Unit]
Description=noVNC bridge for the Omarchy graphical session
After=wayvnc.service
Requires=wayvnc.service
PartOf=graphical-session.target
ConditionEnvironment=WAYLAND_DISPLAY
[Service]
Type=simple
ExecStart=%h/.local/share/omarchy-vnc/websockify-venv/bin/websockify --web=%h/.local/share/omarchy-vnc/noVNC-1.7.0 0.0.0.0:5900 127.0.0.1:5902
Restart=always
RestartSec=3
[Install]
WantedBy=graphical-session.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now omarchy-websockify.serviceThe service restarts with the graphical session and waits for the existing
wayvnc.service unit.
Verify the guest services#
Check both services and request the noVNC page from inside the guest:
systemctl --user is-active wayvnc.service omarchy-websockify.service
curl --fail --silent --show-error --output /dev/null \
http://127.0.0.1:5900/vnc.htmlBoth systemd checks must print active, and the HTTP request must return a
successful response. To inspect a failure, run:
journalctl --user \
--unit wayvnc.service \
--unit omarchy-websockify.service \
--no-pager --lines 100Authenticate your browser#
Open Cua authentication and sign in. Keep the same browser profile open when you connect to the Fleet service.
A Fleet access token or OAuth client ID and secret authenticate the Sandbox SDK. They do not
create a browser session. An unauthenticated browser request to /api/svc redirects to the
interactive sign-in flow.
Open the desktop from the claim#
Open the claim details page in Fleet. When the bound sandbox is ready and the
pool exposes a service named vnc, the page displays a Desktop pane. Wait
for its status to change to Connected, then click or type in the desktop to
confirm that input reaches Omarchy.
If the pane disconnected while the sandbox was starting, select Reconnect.
Open the direct noVNC URL#
You can also open the noVNC page directly. Copy the namespace and bound sandbox name from the claim details page, then replace the placeholders in this URL:
https://run.cua.ai/api/svc/<NAMESPACE>/<SANDBOX_NAME>-vnc/vnc.html?autoconnect=1&resize=scale&reconnect=1The page connects to this WebSocket endpoint through the same authenticated service proxy:
wss://run.cua.ai/api/svc/<NAMESPACE>/<SANDBOX_NAME>-vnc/websockifyUse the bound sandbox name, not the claim name. The -vnc suffix comes from
the Fleet service name.
Troubleshoot the connection#
- The URL redirects to sign-in: sign in at Cua authentication, then reopen the noVNC URL in the same browser profile. SDK OAuth credentials do not replace this step.
- The service URL returns 404: confirm that the namespace and bound sandbox
name match the claim details page and that the pool exposes a service named
vncon port5900. - The page loads but noVNC disconnects: verify that both user services are
active and that ports
5900and5902are listening inside the guest withss --listening --tcp --numeric --processes. - The desktop is black or frozen: confirm that Hyprland is running with
pgrep -a Hyprland, then restartwayvnc.serviceandomarchy-websockify.service. - A native VNC client cannot connect: this is expected. Fleet's service route carries authenticated HTTPS and WebSocket traffic, not a public raw VNC TCP socket. Use the claim's Desktop pane or the noVNC page.
Stop browser access#
To stop the bridge in the guest, disable its user service:
systemctl --user disable --now omarchy-websockify.serviceRemove the vnc entry from the pool's services mapping before you apply the
next pool template. Delete the claim or pool when you no longer need the VM.