[TUTORIAL] Converting a Linux Desktop PC into a Proxmox Server — Preserving Full Desktop Functionality via Native OS in LXC

FredGrekov

New Member
Aug 21, 2026
1
0
1

Concept​

The goal: turn an existing Linux desktop into a Proxmox node without losing the working environment. No OS reinstall, no VM. The native MX-23 installation, residing on its own SSD partitions, is mounted and launched as a privileged LXC container inside Proxmox.

The same physical disk partitions serve two modes:

  • Native boot: GRUB entry → MX-23 loads as a regular desktop OS.​
  • Proxmox mode: GRUB entry → Proxmox loads → MX-23 auto-starts as LXC container 230 with full access to GPU, input devices, audio, and the filesystem.​
No data duplication. No VM overhead. One machine, two modes.


Hardware: Intel X58 / Xeon X5650 / AMD RX-560 4GB / 32GB RAM Proxmox VE: 9.2 (on a native SSD partition, booted via shared GRUB) Guest OS: MX-23 (Debian 12 Bookworm base, migrated to systemd — though it turned out this is not strictly required)


Key Challenges and Solutions​

Most of the work is concentrated in the LXC container config file. Since I am not a Linux/PVE "Sensei," I worked from advice and, after failures, from AI assistants (various ones).

0. Preparing the PC. Installing Proxmox on the PC's Disk​

0.0-- Useful/required: a separate Live-GParted flash drive (Ventoy + GParted ISO).

0.0 On the PC's SSD, free up a few dozen GB and create an LVM partition there. On my SSD — which has many partitions for multiple OSes — I deleted an old unused one and reshuffled GParted to carve out 44 GB. If there is no free space but some partitions have enough slack, booting from Live-GParted lets you easily (though not quickly) rearrange partitions and allocate what you need. Besides LVM, it makes sense to leave a bit of space (1 GB+) for relocating the Proxmox EFI partition.

After changing partitions in GParted — it is useful/necessary to verify the modified partitions (e.g. with GParted itself) and reboot into your native OSes.

0.1 Install Proxmox natively from their ISO to an additional disk. A small spare SSD works fine, but requires opening the case. I didn't have one (summer in the countryside)... so I installed onto a spare 16 GB flash drive. In the PC BIOS, keep the primary boot pointing at the native GRUB; to boot from the Proxmox flash drive, use the BIOS Boot Menu.

0.2 Add a Proxmox boot entry to the native GRUB by copying from the Proxmox grub.conf into the native OS grub.conf, so it boots from the flash drive for now. To avoid "reading tea leaves" — it is useful to consult an AI on how to correctly form this entry for your specific PC configuration and boot setup. You can also copy the EFI partition from the flash drive and try setting up a chainload entry in the native GRUB, or copy the needed entries directly if the bootloaders are compatible. For me they were not (old PC), so AI advice simplified things considerably.

0.3 Boot into Proxmox and move (migrate) the Proxmox LVM partition entirely to the PC's SSD, into the LVM created earlier, using standard LVM commands/utilities (AI advice helps navigate the dark here):

lsblk // lvs // vgs // pvcreate // vgextend // pvmove // vgreduce // pvremove // lvresize ...

For faster transfer — a USB 3.x connection is recommended.

You can also remove the swap logical volume from the Proxmox LVM group if the PC has a native swap — this reclaims 2 GB for PVE storage:


swapoff /dev/pve/swap

lvremove /dev/pve/swap

Then add the native swap to the host's fstab.

If everything goes smoothly, the flash drive will have an empty LVM partition left. Once Proxmox boots normally from the native GRUB, the flash drive is no longer needed and can be removed.

0.4 For convenient subsequent configuration of both PVE and the native OS — installing a light desktop and favorite tools on Proxmox is worthwhile. I installed XFCE, which pulled in everything needed for X. The only "overhead" is disk space, if you don't use those X tools afterward. After this, the default Proxmox boot will land in X, and if you keep X on Proxmox, you should add a separate console-mode Proxmox GRUB entry to the shared GRUB. It can also be useful to install a console mouse driver on both host and guest — it will work in console-mode Midnight Commander.

0.5 On the Proxmox host OS — it is useful/necessary to mount all partitions used by the native OS. Even if there is only root. While rootfs can apparently be mounted from a raw partition — having native OS filesystems accessible via host mount points is convenient and also guards against double-mounting the same partition simultaneously. This is easily done by copying fstab lines from the native OS into the host's fstab. In my case, MX has separate mounts for: home partition, other OS partitions, NTFS, Steam. The "reverse" variant — mounting the PVE LVM into the native OS — may also be useful, though it lacks the dynamic Proxmox filesystems.

Critical: never keep a "raw" partition simultaneously mounted read-write on both the host and the container — this will set the filesystem dirty flag.



1. Initial LXC Config and rootfs from a Native Partition​

To set up the needed LXC config initially, you can create a standard LXC with an OS template type matching your native OS, specify the passthrough and mount entries, and set the primary disk size to 1 GB. That's what I did, starting "in complete darkness" — but that disk is not needed for the guest to operate, and there is no way to remove it afterward. So it may be better to start from scratch, adding mandatory or needed lines based on some test config example. This description also omits network and other infrastructure options.

Proxmox GUI expects rootfs to point to a managed volume. To use a raw partition, bypass the GUI and write directly to the LXC config:

rootfs: /mnt/mx23root,size=0

size=0 disables quota management. The partition must be mounted on the host before the container starts.

Other partitions in my example are mounted like this:

mp0: /mnt/mx23home,mp=/home,shared=1

lxc.mount.entry: /mnt/DATA mnt/DATA none bind,optional,create=dir

lxc.mount.entry: /mnt/Steam-Linux mnt/Steam-Linux none bind,optional,create=dir

Both approaches work fine, and I never managed to get a clear explanation from AI about the advantages of each. I used MP for home on the assumption that PVE manages it more explicitly in that mode.


Since MX-23 is not a standard Proxmox template, and it is important to preserve its native settings, set:

ostype: unmanaged

This prevents Proxmox from trying to modify network configs or /etc/hostname inside the container. It does, however, somewhat limit its "service" capabilities in certain LXC config options.


2. GID When Passing Through /dev/... Devices​

When passing through /dev/... devices, they carry group permissions. These matter when services running as non-root — such as lightdm or render — access those devices. For root and root-owned services, this is irrelevant.

So you need to compare GIDs between the host and the guest. When there are GID mismatches in the groups used by passed-through devices, there are three ways to resolve the "conflict":

  • Use lxc.hook.autodev:... with chown root:{GID} /dev/... — but if X is running on PVE, after starting our LXC those permissions will change for both.​
  • Use GID settings in mount options — but because of unmanaged, not all variants work.​
  • The simplest, most transparent, reliable and safe approach (though I hesitated for several weeks before trying it): use standard commands inside the guest OS to change the GID numbers for the affected groups to match the host's values. When conflicts arise with existing GIDs, shift them further in a chain.​
The "safe" assumption holds because most of these groups are tied specifically to /dev/... devices and will not affect access to ordinary filesystems.


3. AMD GPU Passthrough to LXC​

The situation with Intel is similar; with Nvidia it is probably a bit more involved, but installing the drivers on the host OS after setting up X should make it straightforward.

For LXC (unlike passthrough to a VM) there is no need to blacklist amdgpu on the host. The host must initialize the GPU and create device nodes that the container will bind-mount.

lxc.cgroup2.devices.allow: c 226:0 rwm # /dev/dri/card0

lxc.cgroup2.devices.allow: c 226:128 rwm # /dev/dri/renderD128

lxc.cgroup2.devices.allow: c 235:0 rwm # /dev/kfd (ROCm/Vulkan compute)

lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none bind,optional,create=file

lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file

For RX-560 (Polaris gfx803): ROCm 6.x officially dropped support for this GPU. Use Mesa RADV + Vulkan instead — works excellently for both display (X11) and compute (llama.cpp Vulkan backend).


4. Framebuffer​

lxc.cgroup2.devices.allow: c 29:0 rwm

lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file

lxc.mount.entry: /dev/kfd dev/kfd none bind,optional,create=file


5. Input Devices (Keyboard, Mouse)​

lxc.cgroup2.devices.allow: c 13:* rwm

lxc.mount.entry: /dev/input dev/input none bind,optional,create=dir


6. Audio (ALSA)​

lxc.cgroup2.devices.allow: c 116:* rwm

lxc.mount.entry: /dev/snd dev/snd none bind,optional,create=dir

=====

For other hardware, PCs and OSes, the device numbers and passthrough lines may differ. For example, webcam passthrough is not covered here. Check the relevant ls* commands on the PVE host.

=====

7. Virtual Terminal Allocation (the Display Manager Problem)​

The Proxmox host occupies VT1 (TTY1) — text console — and the standard other TTYs. If the guest LightDM also tries to start on VT1, it sees Seat0 with CanGraphical=no and refuses to launch X11. On top of that, for the guest OS to work as a desktop, it needs at least one "own" TTY for X and another for a text console.

Solution: force the guest display manager to a freed VT and pass the corresponding TTY devices into the container. In the host OS systemd configuration, limit its TTYs to two. The host X VT can optionally be shifted from TTY7 to something else, e.g. TTY6. In the guest OS systemd and Xorg configuration, limit its TTYs to four and direct the DM to, for example, TTY3. This can be done while still in the native boot of the guest OS.

lxc.cgroup2.devices.allow: c 4:3 rwm

lxc.cgroup2.devices.allow: c 4:4 rwm

lxc.mount.entry: /dev/tty0 dev/tty0 none bind,optional,create=file

lxc.mount.entry: /dev/tty3 dev/tty3 none bind,optional,create=file

lxc.mount.entry: /dev/tty4 dev/tty4 none bind,optional,create=file

In /etc/lightdm/lightdm.conf inside the container:

[LightDM]

minimum-vt=3

Critical: always pass through /dev/tty0 — both host and guest use it to identify the currently active TTY!

Result: Proxmox console on Alt+F1 and Alt+F2, MX-23 XFCE desktop on Alt+F3, its text console on Alt+F4. Clean separation.

At this stage it makes sense to use the lxc.init option to start the container in console-only mode. To test X, you can run startx from the guest console or via pct enter .... After testing, revert lxc.init to the standard startup.



8. udev and /sys — The Most Critical Point for Xorg Input via udev!​

This is the step that will cost the most debugging time — both yours and any AI's — if missed.

udev inside the container requires read-write access to /sys. Without it, udev does not start properly! Symptoms:

  • /proc/bus/input/devices correctly lists all input devices (kernel level, always works)​
  • LightDM Seat0 shows CanGraphical=no​
  • X11 cannot find keyboard or mouse​
Fix: lxc.mount.auto: sys:rw gives the container a properly mounted /sys with write access.

lxc.mount.auto: sys:rw

Also mandatory: lxc.autodev: 1 — the container gets a fresh /dev populated by the host's udev rules.

=====

Final Working LXC Config (Key Lines)​

arch: amd64

ostype: unmanaged

hostname: MX-23-1

memory: 11111

cores: 5

swap: 512


features: nesting=1,fuse=1

lxc.apparmor.profile: unconfined

lxc.seccomp.allow_nesting: 1

lxc.autodev: 1

lxc.mount.auto: sys:rw


cmode: tty

console: 1

tty: 4

lxc.tty.max: 2


rootfs: /mnt/mx23root,size=0

mp0: /mnt/mx23home,mp=/home,shared=1


..........

What Works Inside the LXC Container​

  • ✅ Full XFCE desktop on a physical monitor (via bound TTYs)​
  • ✅ AMD RX-560 hardware acceleration (Mesa RADV / Vulkan)​
  • ✅ Keyboard and mouse (full udev enumeration after /sys fix)​
  • ✅ All data partitions (NTFS, ext4) via bind-mount​
  • ✅ ALSA audio​
  • ✅ Steam + Proton games (Vulkan, native libraries)​
  • ✅ All native MX-23 applications, AppImages, scripts​
  • ✅ Docker installed natively works great inside LXC; however, for running Docker containers installed in LXC mode when booting natively into MX-23 — AppImages need to be used​
  • ✅ Scripts that directly control the RX-560 fan via /sys/.../proc/... work perfectly inside LXC​
  • ✅ Network via veth bridge (note: guest IP may change)​
  • ✅ Hardware sensors (lm-sensors) — according to the PVE kernel! For example, in LXC the amdgpu-500 sensor exposes more parameters than in the native boot​

What Does Not Work in LXC​

  • ❌ Loading new kernel modules (insmod/modprobe) — kernel is shared with the host​
  • ❌ Mounting loop devices​


Bonuses from Running a Desktop OS Inside Proxmox LXC​

Compared to a purely native install, running the desktop as an LXC container under Proxmox gives several operational advantages essentially for free:

Any guest runs on the latest PVE kernel. For example, native MX-23 on Debian 12 uses kernel 6.1 — but inside LXC it runs happily on kernel 6.8+!

Snapshots before risky operations. Before updating drivers, the kernel, or major packages — one command (pct snapshot 230 before-kernel-update). Roll back in 30 seconds, no live USB needed.

Resource adjustment without rebooting. During heavy server tasks, reduce CPU cores or RAM for the desktop container via the Proxmox GUI or pct set, then restore afterward. No restart required.

Parallel containers alongside the desktop. On the same hardware, separate LXC containers can run database servers, AI inference (Ollama, llama.cpp), development environments — isolated from the desktop OS.

Console always available. Even if X11 inside the container crashes completely, the Proxmox web console (noVNC) gives access to the container's TTY — no KVM switch or physical keyboard needed.

Clean separation of "desktop" and "server". The Proxmox host stays minimal (no GUI). The desktop OS lives in a container that can be stopped, cloned, or rolled back to a snapshot independently.


Conclusion​

A standard Linux desktop can become a fully functional Proxmox node without giving up its role as a workstation. The native OS lives on its original partitions, runs with full hardware access inside LXC, and remains bootable natively via GRUB whenever bare-metal access is needed.

Two non-obvious blockers in this setup, not described in standard documentation (due to which all AIs — Gemini, the esteemed GPT, and the venerable Claude — mercilessly tortured my defenseless intellect for over a month!):

1. udev requires /sys with write access inside the container — lxc.mount.auto: sys:rw resolves this. Without it, input devices and the display manager fail silently in ways that look externally like GPU or TTY problems.

2. TTY allocation between the host and guest display managers must be explicit — pass specific /dev/ttyN devices, crucially including /dev/tty0, and configure minimum-vt in LightDM accordingly.

Everything else follows standard Linux, kernel, device passthrough, Proxmox, and LXC documentation.