The guide · explanation

Shared apps

8 min read · 2026-10-15 · vmlab 0.9

A shared app is one guest program whose windows appear on the host's own desktop, beside the host's windows, with no VNC console in between. vmlab app run win01 -- notepad.exe opens Notepad as a host window, and closing it ends the session. This chapter explains which protocol carries the windows, what each side needs installed, who the program runs as, and what Windows does differently. The verb and its refusals are in vmlab app.

Sharing an app is something you do to a running machine from the command line. The lab file declares nothing for it, and scripts cannot do it: they run inside the lab daemon, which has no desktop to put a window on.

Which protocol carries the windows

vmlab has no window protocol of its own. Each guest family already has one that sends individual windows to another machine: waypipe for Wayland programs, X11 for X programs, and RDP RemoteApp for Windows. vmlab carries that protocol's bytes over the agent channel. No guest NIC is involved, so a shared app works on a machine with no network at all, and no port is opened on the host or in the guest.

GuestHost sessionProtocolGuest needsHost needs
LinuxWaylandwaypipewaypipe on the login's PATHwaypipe
LinuxWayland, guest or host without waypipeX11, through XwaylandnothingXwayland (DISPLAY set)
LinuxX11X11nothingthe X server
WindowsWayland or X11RDP RemoteAppan edition with an RDP hostFreeRDP 3 (xfreerdp3 or xfreerdp)

vmlab app run reads the host session from its own environment: WAYLAND_DISPLAY set means Wayland, otherwise DISPLAY set means X11. That is why the host end runs in the CLI process and not in a daemon. Only a process started inside your desktop session can reach the compositor or the X server, and the lab daemon stays headless. The daemon opens the agent session and hands the CLI two private unix sockets. The CLI then starts the host end of the protocol against them: waypipe client, a proxy to your X server, or FreeRDP.

The step from waypipe down to X11 is the only automatic one. It happens when the guest has no waypipe for the login, or the host has none, and the host has DISPLAY set. The command says it fell back, and why, on standard error. --via waypipe, --via x11 and --via rdp choose outright and never fall back.

Prefer waypipe on a Wayland host. X11 carries no compression and no shared memory, so a busy program is slower under it, and a program with no X11 backend does not run under it at all.

What to install

On the host, install what the table names for the guests you use. vmlab finds waypipe on PATH, and FreeRDP as xfreerdp3, then xfreerdp. Arch and Debian ship FreeRDP 3 as xfreerdp3; Fedora ships it unsuffixed. To use a binary somewhere else, set waypipe or freerdp in the host configuration file. The CLI reads that file itself, so the change applies to the next vmlab app run with no daemon restart.

~/.config/vmlab/config.wclwcl
import <vmlab-host.wcl>

host {
  waypipe = "/opt/waypipe/bin/waypipe"
  freerdp = "/usr/bin/xfreerdp3"
}

In a Linux guest, install waypipe from the distribution so it is on the login's PATH; X11 needs nothing in the guest. Both ends run with --compress lz4, because a waypipe client and server that disagree on compression refuse each other, so a guest waypipe built without lz4 cannot serve a session. The agent starts waypipe through /bin/sh, which a minimal container image must still have.

In every guest, the agent must be one built with shared apps. vmlab machine capabilities <machine> lists app_waypipe and app_x11 for a Linux agent and app_rdp for a Windows one. An older agent is refused by name, and vmlab machine repair-agent replaces it in a running VM; see The machine's agent is out of date.

Who the program runs as

A shared app runs as a declared login, resolved exactly as for exec and shell: --user and --password, else the machine's default login, else the agent identity. Logins explains the ladder. The waypipe check runs as the same identity, because the PATH that matters is that login's.

RDP cannot log on as the agent identity, which is SYSTEM. A Windows machine with no login {} and no --user, or with --user SYSTEM, is refused with RDP needs an account. Declare a login with its password, or pass --user and --password.

Windows: editions and the console session

Windows Home has no RDP host, so it cannot share an app. The agent reads the edition from the registry's EditionID and refuses a Home edition by name. Pro, Enterprise, Education and Server editions work.

Each RDP session starts with the agent preparing the guest. It turns Remote Desktop on and allows RemoteApp to start programs that are not on a published list (the fAllowUnlistedRemotePrograms policy and TSAppAllowList's fDisabledAllowList). It adds the account to Remote Desktop Users when it is not an administrator, and starts TermService. It changes each setting only where it differs, and leaves it changed afterwards. RDP traffic reaches the guest's own 127.0.0.1:3389 from the agent, so no firewall rule is touched. FreeRDP gets the login's credentials on its standard input, never on its command line.

The first shared app on a machine is slower to appear. Turning Remote Desktop on starts TermService, whose listener takes about 40 seconds to accept a connection. The agent waits for it, and app run prints [<machine>] waiting for Remote Desktop to listen on 127.0.0.1:3389 while it does. FreeRDP starts once the listener is up.

RemoteApp is a real remote logon, and that has a visible cost, because Windows allows one session per account. A client edition always does, and a Server edition does while "Restrict each user to a single session" is on, which is its default. The RDP logon disconnects the same account's console session, so vmlab console shows the lock screen while an app is shared. A second vmlab app run against the same machine and account takes that session over. The first app's windows move into the new FreeRDP process, the old one exits, and the first command returns. vmlab states this rather than working around it, because it is how Windows behaves. On a Server with that policy turned off, each app run gets its own session.

Closing FreeRDP, or pressing Ctrl-C on app run, disconnects the Windows session rather than logging it off. The program keeps running in it, and its windows come back with the next shared app for that account on that machine. Close the program's own windows to end it.

X11 and the host display

For an X11 session the CLI mints a random cookie and the agent writes it into a private Xauthority file for the program. The agent claims the first free display from :10 up. Every X connection the program makes reaches the CLI, which checks the cookie and drops a connection that carries the wrong one. It then swaps in your real display's cookie and connects to your X server, the way OpenSSH forwards X11. A guest process that was not given the cookie cannot reach your display.

An X11 app can see your other X windows

Once connected, the guest program has the same reach into the host X server as ssh -X, including reading input sent to other X clients. vmlab is not a security boundary between you and your guests, but this is one more reason to prefer waypipe on a Wayland host.

Worked examples

A Linux VM on a Wayland desktop

Install waypipe and a program in the guest as root, then run the program as the machine's default login. The window opens on your desktop and app run returns when you close it, with the program's exit code.

sh
vmlab exec --user root web -- apt-get install -y waypipe gnome-text-editor
vmlab app run web -- gnome-text-editor

On an X11 desktop, or with --via x11, the same command runs over X11 and the guest needs no waypipe.

A container

A lab container shares apps like any Linux guest. Its sockets live inside the container's root filesystem, where the program can reach them. Add waypipe and the program to the image, or install them into the running container:

sh
vmlab exec --user root dev01 -- apk add waypipe foot
vmlab app run dev01 -- foot
vmlab app run --detach dev01 -- foot

The third line hands the session to a background helper and gives the terminal back. The helper ends when foot exits.

A Windows VM

The machine needs an account with a password for RDP to log on as, so declare one:

vmlab.wclwcl
vm "win01" {
  template = "x86_64/windows-server-2025"
  nic { segment = "lan" }

  login "dev" { user = "dev" password = "vmlab123!" default = true }
}
sh
vmlab app run win01 -- notepad.exe
vmlab app run --cwd 'C:\Users\dev' win01 -- notepad.exe notes.txt

The provision that creates dev must run before the first shared app, as it must before vmlab exec runs as that login. app run returns when FreeRDP exits, with FreeRDP's exit code.

What shared apps do not do