Arch XFCE Desktop over RDP with Podman
Install guide
You need Podman (to run the container) and an RDP client (to connect to it).
sudo apt update
sudo apt install -y podman freerdp-sdl curl ca-certificates
podman version
sdl-freerdp /versionArch image has no systemd. sleep infinity keeps the container alive so we can work inside it. We publish port 13390 on the host → 3389 inside the container.
podman pull docker.io/library/archlinux:latest
# podman rm -f arch-rdp 2>/dev/null || true
podman run -d \
--name arch-rdp \
--hostname arch-rdp \
--restart unless-stopped \
-p 13390:3389 \
docker.io/library/archlinux:latest \
sleep infinityCheck it’s running and the port is open:
podman ps --filter name=arch-rdpFresh Arch has an empty keyring → packages can’t be verified. We also install the desktop, Xorg, build tools, and helpers. (xrdp itself is not in official repos.)
podman 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
'makepkg (AUR builds) refuses to run as root. This user will also be the one we log into via RDP. Passwordless sudo lets it install packages cleanly.
podman 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
'xrdp is the actual RDP server (listens on 3389, handles login, starts sessions). Must be built first.
podman 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 is the Xorg graphics driver that lets the RDP session actually show a desktop. Needs a PGP key for verification.
Preferred (with key):
podman 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):
# same as above but add --skippgpcheck to makepkgVerify both packages are installed:
podman exec arch-rdp bash -lc 'pacman -Q xrdp xorgxrdp'After login, xrdp needs to know to start XFCE. We overwrite the default script and create a simple user session file.
podman 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 only has sleep as its main process. We must manually start the RDP services (as root).
podman 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 can see the port:
ss -lntp | grep 13390
podman port arch-rdpWindowed + dynamic resolution.
sdl-freerdp \
/v:127.0.0.1:13390 \
/u:archuser \
/p:archuser \
/cert:ignore \
/size:1280x800 \
/t:"Arch Podman RDP" \
+dynamic-resolution \
+window-dragIf something fails, check logs inside the container:
podman exec arch-rdp tail -80 /var/log/xrdp-sesman.log
podman exec arch-rdp tail -80 /var/log/xrdp.logSo files on the desktop survive if we rebuild the container later. :Z is for SELinux compatibility.
mkdir -p ~/arch-rdp-homeLater (after commit) you’ll recreate the container with:
-v ~/arch-rdp-home:/home/archuser:ZSaves all the packages + config so you never have to rebuild from scratch again.
podman 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
'
podman commit arch-rdp localhost/arch-rdp:xfce
podman images localhost/arch-rdpAfter stop/start or reboot you won’t have to manually run step 7 every time. Also cleans stale X11 sockets that break sessions.
podman 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'Commit again:
podman commit arch-rdp localhost/arch-rdp:xfceThen recreate the container once with the entrypoint (and optional home mount):
podman rm -f arch-rdp
podman run -d \
--name arch-rdp \
--hostname arch-rdp \
--restart unless-stopped \
-p 13390:3389 \
-v ~/arch-rdp-home:/home/archuser:Z \
localhost/arch-rdp:xfce \
/usr/local/bin/arch-rdp-entry.shpodman stop arch-rdp
podman start arch-rdp
# wait a couple seconds, then connect with the same sdl-freerdp commandLogin credentials: archuser / archuser
Connect address: 127.0.0.1:13390