[Proxmox Series Part 6] Standardizing LXC Containers and Their Boundaries

한국어 버전

Once your Windows VM template is in place, the next practical step is standardizing LXC containers for lighter services. LXC shares the host kernel, so it’s fast and efficient, but a sloppy privilege model or storage boundary jeopardizes the entire host. Enabling nesting, privileged mode, or bind mounts without a plan often removes the isolation you counted on and makes backups messy.

This article lays out the baseline I recommend when creating LXC containers in Proxmox. We will cover template choices, why unprivileged containers should be the default, how to size memory and disk with cgroup limits, criteria for bind mounts and device passthrough, when nesting is justified, and which workloads should never live in LXC—along with practical pct commands you can reuse.

How this post flows

  1. Why LXC needs a standard
  2. Template choices and initial packages
  3. Default unprivileged configuration
  4. Resource allocation and storage planning
  5. When to use nesting, bind mounts, or device passthrough
  6. Workloads that do not belong in LXC

Terms introduced here

  1. Unprivileged container: A container whose internal root UID/GID is mapped to non-root IDs on the host, reducing privilege exposure.
  2. Bind mount: A host directory mounted inside the container filesystem, requiring careful permission handling.
  3. Nesting: Allowing containers to run Docker, LXC, or full systemd units inside themselves—convenient but weaker security.
  4. cgroup limits: CPU, memory, and I/O quotas enforced through Linux control groups to keep containers within bounds.
  5. idmap: The UID/GID mapping table Proxmox uses to translate container identities to host identities.

Reading card

  • Estimated time: 15 minutes
  • Prereqs: Proxmox VE 8.x, updated LXC template repo, ZFS or LVM storage available, knowledge of whether your physical switch supports VLANs
  • After reading: you can build a reusable LXC template, decide when to enable nesting/bind mounts, and apply a consistent backup/security checklist.

Why LXC needs a standard

LXC boots faster and uses fewer resources than a VM, but you are responsible for the isolation details. Without a standard, each container ends up with different privilege settings, storage paths, and network behaviors, which complicates backups and audits.

Objectives

  1. Run every container unprivileged by default.
  2. Refresh templates and base packages regularly so security fixes are not missed.
  3. Declare cgroup limits explicitly to prevent invisible overcommit.

Template choices and initial packages

After pveam update, the Templates tab lets you pull official images. To keep things consistent:

  • Distribution: Prefer Debian or Ubuntu LTS. Rolling distros add long-term risk.
  • Version tracking: Note the year/month in the template name so you remember when it was pulled.
  • Base packages: Install curl, git, htop, the container edition of qemu-guest-agent, and chrony for sane defaults. Align SSH ports, admin accounts, and banner text in the template so derived containers stay consistent.

Once the container is configured, use pct template or clone it so you can recreate the same setup quickly. Add the distro, month, and patch level to the template name, and track provisioning scripts in Git/Ansible to reduce drift.

# Example: build a Debian 12 template in Feb 2025
pveam download local debian-12-standard_12.2-1_amd64.tar.zst
pct create 120 local:vztmpl/debian-12-standard_12.2-1_amd64.tar.zst \
  -unprivileged 1 -cores 2 -memory 512 -swap 0 \
  -net0 name=eth0,bridge=vmbr1,firewall=1
pct exec 120 -- apt-get update && apt-get install -y curl git htop chrony qemu-guest-agent
pct template 120

Default unprivileged configuration

Set unprivileged=1 when running pct create, and only use privileged containers when a specific workload demands direct kernel access or device passthrough. Unprivileged containers offer three major benefits:

  1. Container root does not map to host root, shrinking the attack surface.
  2. Backups hit fewer ACL and permission conflicts.
  3. Proxmox manages UID/GID mappings automatically, making user management easier.

When bind mounts are involved, ensure mp0 / mp1 definitions include the correct uid/gid mapping so write access works as expected. For example: mp0: /tank/logs,mp=/var/log/app,uid=1000,gid=1000,backup=1.

Heads-up: bind-mounted paths are skipped by vzdump by default. Even with backup=1, the data still lives on the host dataset, so create snapshots/backups on the host side as well.

Resource allocation and storage planning

Because LXC feels lightweight, people often leave resources unlimited. Set boundaries early:

  • Memory: Minimum 256 MB; most services start comfortably at 512 MB–1 GB. Set swap to 0 when physical RAM is tight, and consider host-level zram swap instead.
  • CPU: Start with cores=1 or 2, and define cpulimit/cpuunits so one container cannot hog the host. For latency-sensitive apps, pin them to a NUMA node with cpuset.
  • Disk: Allocate at least 8 GB. If logs grow quickly, mount a dedicated dataset or attach another disk instead of bloating the root filesystem. Directory-based rootfs is fast, but ZFS/LVM block storage is safer for snapshots/backups.
  • Storage type: Enable compression=lz4 and atime=off on ZFS datasets; on LVM-thin, monitor thin pool usage so snapshots do not exhaust the pool.

When to use nesting, bind mounts, or device passthrough

Nesting

  • Use it when the container must run Docker, LXD, or other second-level container managers.
  • Risk: Much weaker isolation because Docker’s cgroup features map straight onto the host. Kernel CVEs, cgroup bugs, or systemd exploits can affect the entire host.
  • Alternative: Move the workload into a KVM VM, or run it as a simple process without nesting if you only need a single service.

Bind mounts

  • Use them to share host datasets (ZFS, NAS mounts) for logs or data directories.
  • Caution: Specify mp0=/tank/data,mp=/data,uid=1000,gid=1000,backup=1 so ownership matches the container. Document who can modify the host dataset; deleting or chmod’ing the host path immediately affects the container.

Device passthrough

  • Use it for USB Zigbee sticks, serial adapters, or other hardware a service must reach directly.
  • Caution: Prefer the usbX options over raw /dev/ttyUSB0 to keep hotplug stable, and isolate that device into its own container so failures stay compartmentalized. If you must switch to privileged mode, review firewall/AppArmor rules.

Workloads that do not belong in LXC

Some workloads are better off in VMs:

  1. Kernel-module-heavy services: ZFS, WireGuard, FUSE, or anything that loads custom modules should run inside a VM. Loading modules from a container modifies the host kernel.
  2. Primary databases: PostgreSQL or MySQL with critical data benefit from VM-level isolation and predictable I/O semantics. UID/GID remapping mistakes in containers complicate recovery.
  3. Strong multi-tenancy: Different teams or tenants need VM boundaries (or even physical separation), not LXC permissions.
  4. Nested virtualization: If you must run KVM/Hyper-V again, do it inside a VM—LXC does not support nested virtualization.

LXC shines for network utilities, proxies, lightweight app servers, monitoring stacks, and authentication proxies—services with simple state and minimal kernel dependencies. Even for these, keep them behind the same firewall policies as VMs (PVE firewall rules or an external firewall) and apply the same backup cadence.

Wrap-up

LXC’s speed and density only pay off when the underlying rules are consistent. Establish a template, keep everything unprivileged unless proven otherwise, and apply the same resource and permission checklist every time. In the next article we will zoom out to network design: how bridges work, how to carve private networks, where OPNsense fits, and how VPN-based remote access changes your Proxmox layout.

💬 댓글

이 글에 대한 의견을 남겨주세요