Install guide for running a full Arch Linux XFCE desktop inside a WSL container, accessed via RDP from Windows.
Adapted from the Podman version of this guide.
| Requirement | Notes |
|---|---|
| Windows 11 (or Windows with WSL 2.9.3+) | WSL containers are a pre-release feature |
wslc.exe on PATH |
Ships with WSL ≥ 2.9.3 |
| RDP client | Built-in Remote Desktop Connection (mstsc) is enough |
| Disk space | Final image is roughly 2 GB |
| Time | First setup: ~20–40 minutes (AUR builds dominate) |
Open PowerShell (normal or Admin):
wsl --update --pre-release
wsl --version
wslc versionYou want WSL 2.9.3 or higher and a working wslc binary (typically C:\Program Files\WSL\wslc.exe).
| Podman | wslc |
|---|---|
podman pull |
wslc pull |
podman run -d |
wslc run -d |
podman exec |
wslc exec |
podman ps |
wslc list / wslc ps |
podman commit |
wslc export + wslc import (no commit) |
podman stop / start |
wslc stop / wslc start |
podman rm -f |
wslc remove -f / wslc rm -f |
--restart unless-stopped |
Not available — start the container after reboot |
-v path:path:Z |
-v path:path (no SELinux :Z on Windows) |
All host-side commands below are for PowerShell. Container-side scripts use bash.
Confirm tooling:
wslc version
# Optional FreeRDP (if you prefer CLI over mstsc):
# winget install FreeRDP.FreeRDPCreate a folder for a persistent home directory (used later):
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\arch-rdp-home" | Out-NullThe official Arch image has no systemd. sleep infinity keeps the container alive so you can exec into it. Port 13390 on the host maps to 3389 (RDP) inside the container.
wslc pull docker.io/library/archlinux:latest
# Clean up a previous attempt if needed
wslc remove -f arch-rdp 2>$null
wslc run -d `
--name arch-rdp `
--hostname arch-rdp `
-p 13390:3389 `
docker.io/library/archlinux:latest `
sleep infinityCheck that it’s running:
wslc list
# CONTAINER ID NAME IMAGE STATUS PORTS
# ... arch-rdp archlinux:latest running 127.0.0.1:13390->3389/tcpFresh Arch has an empty keyring, so packages can’t be verified until you init/populate keys. Install the desktop, Xorg, build tools, and helpers. xrdp itself is not in the official repos (AUR, next steps).
wslc exec -it arch-rdp bash -lc @'
set -euo pipefail
pacman-key --init
pacman-key --populate archlinux
pacman -Syu --noconfirm
pacman -S --noconfirm --needed \
base-devel git sudo which net-tools iproute2 \
fakeroot debugedit \
xfce4 xfce4-session xfce4-terminal \
xorg-server xorg-xinit xorg-xauth \
tigervnc dbus
'@Tip: If
pacman-key --initis slow, entropy is low — leave it running; it usually finishes.
makepkg (AUR builds) refuses to run as root. This user is also who you log in as over RDP. Passwordless sudo keeps package installs smooth.
wslc exec -it arch-rdp bash -lc @'
set -euo pipefail
id archuser >/dev/null 2>&1 || useradd -m -s /bin/bash archuser
echo "archuser:archuser" | chpasswd
echo "archuser ALL=(ALL) NOPASSWD: ALL" >/etc/sudoers.d/archuser
chmod 440 /etc/sudoers.d/archuser
'@Credentials (change later if you care):
| Field | Value |
|---|---|
| User | archuser |
| Password | archuser |
xrdp is the RDP server (listens on 3389, handles login, starts sessions).
wslc exec -it -u archuser -w /home/archuser arch-rdp bash -lc @'
set -euo pipefail
rm -rf xrdp
git clone --depth 1 https://aur.archlinux.org/xrdp.git
cd xrdp
makepkg -si --noconfirm --needed
'@This can take several minutes.
xorgxrdp is the Xorg driver that lets the RDP session show a real desktop. It may need a PGP key for verification.
wslc exec -it -u archuser -w /home/archuser arch-rdp bash -lc @'
set -euo pipefail
gpg --keyserver keyserver.ubuntu.com \
--recv-keys 61ECEABBF2BB40E3A35DF30A9F72CDBC01BF10EB
rm -rf xorgxrdp
git clone --depth 1 https://aur.archlinux.org/xorgxrdp.git
cd xorgxrdp
makepkg -si --noconfirm --needed
'@wslc exec -it -u archuser -w /home/archuser arch-rdp bash -lc @'
set -euo pipefail
rm -rf xorgxrdp
git clone --depth 1 https://aur.archlinux.org/xorgxrdp.git
cd xorgxrdp
makepkg -si --noconfirm --needed --skippgpcheck
'@Verify both packages:
wslc exec arch-rdp bash -lc 'pacman -Q xrdp xorgxrdp'Expected something like:
xrdp 0.10.x-1
xorgxrdp 0.10.x-1
After login, xrdp must start XFCE. Overwrite the default session script and add a simple user .xsession.
wslc exec -it arch-rdp bash -lc @'
set -euo pipefail
cat >/etc/xrdp/startwm.sh << "EOF"
#!/bin/sh
unset DBUS_SESSION_BUS_ADDRESS
unset XDG_RUNTIME_DIR
export XDG_SESSION_TYPE=x11
export DESKTOP_SESSION=xfce
export XDG_CURRENT_DESKTOP=XFCE
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
mkdir -p "$XDG_RUNTIME_DIR"
chmod 700 "$XDG_RUNTIME_DIR"
if command -v dbus-run-session >/dev/null 2>&1; then
exec dbus-run-session -- startxfce4
fi
exec startxfce4
EOF
chmod +x /etc/xrdp/startwm.sh
printf "%s\n" "#!/bin/sh" "exec startxfce4" >/home/archuser/.xsession
chown archuser:archuser /home/archuser/.xsession
chmod +x /home/archuser/.xsession
mkdir -p /etc/X11
printf "%s\n" "allowed_users=anybody" "needs_root_rights=yes" \
>/etc/X11/Xwrapper.config 2>/dev/null || true
'@The container’s main process is still sleep. Start the RDP services manually as root.
wslc exec -it arch-rdp bash -lc @'
set -euo pipefail
mkdir -p /run/dbus /var/run/xrdp /var/log
rm -f /run/dbus/pid /var/run/xrdp/*.pid /var/run/xrdp-sesman.pid 2>/dev/null || true
if [ ! -S /run/dbus/system_bus_socket ]; then
dbus-daemon --system --fork || true
fi
pgrep -x xrdp-sesman >/dev/null || xrdp-sesman
pgrep -x xrdp >/dev/null || xrdp
pgrep -a xrdp
ss -lntp | grep -E "3389|3350" || true
'@Confirm the host port:
wslc list
# Should show: 127.0.0.1:13390->3389/tcp
# Optional: check Windows is listening
netstat -an | findstr 13390- Press
Win + R, run:mstsc - Computer:
127.0.0.1:13390 - User name:
archuser - Password:
archuser - If warned about the certificate, accept/continue
Some builds of
mstscwant the port in the Show Options → Advanced dialog or as127.0.0.1:13390in the Computer field. Both usually work.
xfreerdp /v:127.0.0.1:13390 /u:archuser /p:archuser /cert:ignore /size:1280x800 /t:"Arch WSLC RDP" +dynamic-resolutionwslc exec arch-rdp bash -lc 'tail -80 /var/log/xrdp-sesman.log'
wslc exec arch-rdp bash -lc 'tail -80 /var/log/xrdp.log'Common fixes:
- Re-run step 7 (services died after a stop/start).
- Black screen: confirm
startwm.shand.xsessionfrom step 6. - Connection refused: container not running or port mapping missing (
wslc list).
So desktop files survive rebuilds. You already created the host folder in step 0:
# Host path
# C:\Users\<you>\arch-rdp-home → /home/archuser inside the containerYou’ll attach it when recreating the container in step 11:
-v "$env:USERPROFILE\arch-rdp-home:/home/archuser"
Note: If you mount an empty host folder over
/home/archuseron a fresh recreate, you get an empty home. Either copy the container home out first (wslc container cp) or log in once and reconfigure. Best approach: finish steps 10–11, then mount home on the recreated container so the image still has defaults and the volume overlays for persistence.
wslc has no commit command. Use export + import instead. This flattens the container filesystem into a new image (loses layer history; fine for this use case).
wslc exec arch-rdp bash -lc @'
pacman -Scc --noconfirm 2>/dev/null || true
rm -rf /home/archuser/xrdp /home/archuser/xorgxrdp /tmp/* 2>/dev/null || true
'@wslc stop arch-rdp
$tar = Join-Path $env:TEMP 'arch-rdp-commit.tar'
if (Test-Path $tar) { Remove-Item $tar -Force }
Write-Host "Exporting (can take a few minutes)..."
wslc export -o $tar arch-rdp
Write-Host "Importing as arch-rdp:xfce ..."
wslc import $tar arch-rdp:xfce
wslc images
# REPOSITORY TAG SIZE
# arch-rdp xfce ~2 GB
# archlinux latest ~400 MBOptional: free the temp tar (~2 GB):
Remove-Item $tar -ErrorAction SilentlyContinueWithout this, every wslc start forces you to re-run step 7. Install a small entrypoint, then re-export/import (or install it before step 10 to avoid a second snapshot).
Start the old container if needed:
wslc start arch-rdpwslc exec arch-rdp bash -lc @'
cat >/usr/local/bin/arch-rdp-entry.sh << "EOF"
#!/bin/bash
mkdir -p /run/dbus /var/run/xrdp /var/log /tmp/.X11-unix
chmod 1777 /tmp /tmp/.X11-unix 2>/dev/null || true
rm -f /tmp/.X11-unix/X* /tmp/.X*-lock 2>/dev/null || true
rm -f /run/dbus/pid /var/run/xrdp/*.pid /var/run/xrdp-sesman.pid 2>/dev/null || true
if id archuser >/dev/null 2>&1; then
chown -R archuser:archuser /home/archuser 2>/dev/null || true
fi
if [ ! -S /run/dbus/system_bus_socket ]; then
dbus-daemon --system --fork || true
fi
pgrep -x xrdp-sesman >/dev/null 2>&1 || xrdp-sesman || true
pgrep -x xrdp >/dev/null 2>&1 || xrdp || true
exec sleep infinity
EOF
chmod +x /usr/local/bin/arch-rdp-entry.sh
'@wslc stop arch-rdp
$tar = Join-Path $env:TEMP 'arch-rdp-commit.tar'
wslc export -o $tar arch-rdp
wslc import $tar arch-rdp:xfce
Remove-Item $tar -ErrorAction SilentlyContinuewslc remove -f arch-rdp
wslc run -d `
--name arch-rdp `
--hostname arch-rdp `
-p 13390:3389 `
-v "$env:USERPROFILE\arch-rdp-home:/home/archuser" `
arch-rdp:xfce `
/usr/local/bin/arch-rdp-entry.shWait a couple of seconds, then verify:
wslc list
wslc exec arch-rdp bash -lc 'pgrep -a xrdp'You should see xrdp-sesman and xrdp. Connect as in step 8.
Home mount note: The first time you attach an empty
arch-rdp-home, it masks the image’s/home/archuser. Either:
- Skip the
-vuntil you’ve logged in once and then copy the home out, or- Accept a fresh home and re-create only what you need (
.xsessionis in the image’s home — if masked empty, recreate it):wslc exec -u archuser arch-rdp bash -lc 'printf "%s\n" "#!/bin/sh" "exec startxfce4" > ~/.xsession; chmod +x ~/.xsession'
# Stop
wslc stop arch-rdp
# Start (entrypoint brings xrdp up)
wslc start arch-rdp
# Wait ~2 seconds, then connect
mstsc /v:127.0.0.1:13390| Item | Value |
|---|---|
| Address | 127.0.0.1:13390 |
| User | archuser |
| Password | archuser |
| Image | arch-rdp:xfce |
| Home on host | %USERPROFILE%\arch-rdp-home |
wslc list -a # all containers
wslc images # images
wslc logs arch-rdp # container logs (if any)
wslc exec -it arch-rdp bash # root shell
wslc exec -it -u archuser arch-rdp bash
wslc stats arch-rdp # resource usewslc has no restart policy. Start the container again:
wslc start arch-rdpIf the WSL container VM was shut down entirely, the first wslc command may take a moment to bring it back.
wslc remove -f arch-rdp
wslc rmi arch-rdp:xfce
# optional: wipe home
# Remove-Item -Recurse -Force "$env:USERPROFILE\arch-rdp-home"Then start again at step 1.
netstat -an | findstr 13390
# Change host port, e.g. -p 13391:3389wslc exec arch-rdp bash -lc 'pgrep -a xrdp || true; /usr/local/bin/arch-rdp-entry.sh &'
# Better: ensure the container was started with the entrypoint command (step 11c)
wslc inspect arch-rdp- Confirm XFCE packages:
wslc exec arch-rdp bash -lc 'pacman -Q xfce4-session' - Re-apply step 6 (
startwm.sh/.xsession) - Check:
wslc exec arch-rdp bash -lc 'tail -100 /var/log/xrdp-sesman.log'
Use the skippgpcheck fallback in step 5 (lab only), or import the correct key from the package’s AUR page.
Normal for a full desktop (~2 GB tar). Clean caches in step 10 first. Keep the image; delete the temp tar.
wsl --update --pre-release
where.exe wslc
# Expect: C:\Program Files\WSL\wslc.exeClose and reopen the terminal if PATH is stale.
Save as wslc-commit.ps1:
param(
[Parameter(Mandatory = $true)][string]$Container,
[Parameter(Mandatory = $true)][string]$Image
)
$tar = Join-Path $env:TEMP ("wslc-commit-{0}.tar" -f $Container)
Write-Host "Stopping $Container (if running)..."
wslc stop $Container 2>$null
Write-Host "Exporting $Container -> $tar"
wslc export -o $tar $Container
Write-Host "Importing $tar -> $Image"
wslc import $tar $Image
Remove-Item $tar -Force -ErrorAction SilentlyContinue
Write-Host "Done. Image:"
wslc imagesUsage:
.\wslc-commit.ps1 -Container arch-rdp -Image arch-rdp:xfce┌─ Windows host ─────────────────────────────────────┐
│ mstsc / FreeRDP → 127.0.0.1:13390 │
│ │ │
│ ▼ │
│ wslc port publish 13390 → container :3389 │
│ │ │
│ ┌─ WSL container VM / arch-rdp ─────────────────┐ │
│ │ xrdp + xrdp-sesman │ │
│ │ xorgxrdp + XFCE (startxfce4) │ │
│ │ user: archuser │ │
│ │ volume: %USERPROFILE%\arch-rdp-home │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Based on the Podman Arch RDP guide by timsonner. Adapted for Microsoft WSL containers (wslc) on Windows.