Skip to content

Instantly share code, notes, and snippets.

@timsonner
Created August 7, 2026 04:51
Show Gist options
  • Select an option

  • Save timsonner/1b4a09f92d94e24def5d82fe29b7c5db to your computer and use it in GitHub Desktop.

Select an option

Save timsonner/1b4a09f92d94e24def5d82fe29b7c5db to your computer and use it in GitHub Desktop.
Install guide for running a full Arch Linux XFCE desktop inside a WSL container, accessed via RDP from Windows.

Arch XFCE Desktop over RDP with WSL Containers (wslc)

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.


Prerequisites

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)

Install / update WSL containers

Open PowerShell (normal or Admin):

wsl --update --pre-release
wsl --version
wslc version

You want WSL 2.9.3 or higher and a working wslc binary (typically C:\Program Files\WSL\wslc.exe).

Differences from Podman (quick map)

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.


0. Prepare the host

Confirm tooling:

wslc version
# Optional FreeRDP (if you prefer CLI over mstsc):
# winget install FreeRDP.FreeRDP

Create a folder for a persistent home directory (used later):

New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\arch-rdp-home" | Out-Null

1. Pull Arch and start a long-running container

The 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 infinity

Check that it’s running:

wslc list
# CONTAINER ID   NAME       IMAGE              STATUS    PORTS
# ...            arch-rdp   archlinux:latest   running   127.0.0.1:13390->3389/tcp

2. Initialize pacman keys + install base packages

Fresh 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 --init is slow, entropy is low — leave it running; it usually finishes.


3. Create the user archuser

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

4. Build & install xrdp (from AUR)

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.


5. Build & install xorgxrdp (from AUR)

xorgxrdp is the Xorg driver that lets the RDP session show a real desktop. It may need a PGP key for verification.

Preferred (with key)

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
'@

Fallback (skip signature check — lab only)

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

6. Configure the session startup script

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
'@

7. Start xrdp (no systemd)

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

8. Connect with an RDP client

Option A — Windows Remote Desktop (recommended)

  1. Press Win + R, run: mstsc
  2. Computer: 127.0.0.1:13390
  3. User name: archuser
  4. Password: archuser
  5. If warned about the certificate, accept/continue

Some builds of mstsc want the port in the Show Options → Advanced dialog or as 127.0.0.1:13390 in the Computer field. Both usually work.

Option B — FreeRDP (if installed)

xfreerdp /v:127.0.0.1:13390 /u:archuser /p:archuser /cert:ignore /size:1280x800 /t:"Arch WSLC RDP" +dynamic-resolution

If the session fails

wslc 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.sh and .xsession from step 6.
  • Connection refused: container not running or port mapping missing (wslc list).

9. (Optional) Persist the user’s home directory

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 container

You’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/archuser on 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.


10. “Commit” the working container into a reusable image

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).

Clean caches / build trees first

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
'@

Export and import

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 MB

Optional: free the temp tar (~2 GB):

Remove-Item $tar -ErrorAction SilentlyContinue

11. (Recommended) Entrypoint so xrdp starts automatically

Without 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).

11a. Write the entrypoint into the live container

Start the old container if needed:

wslc start arch-rdp
wslc 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
'@

11b. Snapshot again (so the image includes the entrypoint)

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 SilentlyContinue

11c. Recreate the container from the image

wslc 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.sh

Wait 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:

  1. Skip the -v until you’ve logged in once and then copy the home out, or
  2. Accept a fresh home and re-create only what you need (.xsession is 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'

Day-to-day usage

# 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

Useful commands

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 use

After a Windows reboot

wslc has no restart policy. Start the container again:

wslc start arch-rdp

If the WSL container VM was shut down entirely, the first wslc command may take a moment to bring it back.


Rebuild from scratch (clean slate)

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.


Troubleshooting

Port already in use

netstat -an | findstr 13390
# Change host port, e.g. -p 13391:3389

xrdp not listening after start

wslc 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

Black / blank RDP session

  • 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'

AUR makepkg fails on PGP

Use the skippgpcheck fallback in step 5 (lab only), or import the correct key from the package’s AUR page.

Export/import is huge or slow

Normal for a full desktop (~2 GB tar). Clean caches in step 10 first. Keep the image; delete the temp tar.

wslc not found after update

wsl --update --pre-release
where.exe wslc
# Expect: C:\Program Files\WSL\wslc.exe

Close and reopen the terminal if PATH is stale.


Optional: one-shot “commit” helper (PowerShell)

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 images

Usage:

.\wslc-commit.ps1 -Container arch-rdp -Image arch-rdp:xfce

Architecture summary

┌─ 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          │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

License / credit

Based on the Podman Arch RDP guide by timsonner. Adapted for Microsoft WSL containers (wslc) on Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment