Skip to content

Instantly share code, notes, and snippets.

@timsonner
Last active July 28, 2026 05:28
Show Gist options
  • Select an option

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

Select an option

Save timsonner/e96506ff5ecf2aba9f02e8253f3f109a to your computer and use it in GitHub Desktop.
Arch desktop over RDP with Podman

Arch XFCE Desktop over RDP with Podman
Install guide


0. Prepare the host

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 /version

1. Pull Arch and start a long-running container

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

Check it’s running and the port is open:

podman ps --filter name=arch-rdp

2. Initialize pacman keys + install base packages

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

3. Create the user archuser

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
'

4. Build & install xrdp (from AUR)

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
'

5. Build & install xorgxrdp (from AUR)

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 makepkg

Verify both packages are installed:

podman exec arch-rdp bash -lc 'pacman -Q xrdp xorgxrdp'

6. Configure the session startup script

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
'

7. Start xrdp (no systemd)

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-rdp

8. Connect with the RDP client

Windowed + 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-drag

If 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.log

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

So files on the desktop survive if we rebuild the container later. :Z is for SELinux compatibility.

mkdir -p ~/arch-rdp-home

Later (after commit) you’ll recreate the container with:

-v ~/arch-rdp-home:/home/archuser:Z

10. Commit the working container into a reusable image

Saves 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-rdp

11. (Recommended) Add an entrypoint so xrdp starts automatically

After 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:xfce

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

Day-to-day after everything works

podman stop arch-rdp
podman start arch-rdp
# wait a couple seconds, then connect with the same sdl-freerdp command

Login credentials: archuser / archuser
Connect address: 127.0.0.1:13390

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