plugins/proxmox/skills/proxmox-lxc/SKILL.md
Use this skill when the user asks about Proxmox VE, LXC containers, creating containers on Proxmox, running Docker in LXC, setting up a lightweight host for services like cloudflared, or managing Proxmox infrastructure. For deploying services inside the LXC via docker-compose, also recall the arcane plugin's arcane-gitops skill. For Cloudflare Tunnel setup, recall the cloudflare plugin's cloudflare-tunnels skill.
npx skillsauth add nsheaps/ai-mktpl proxmox-lxcInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Proxmox VE (PVE) is an open-source server virtualization platform built on Debian. It supports both KVM virtual machines and LXC containers — lightweight, OS-level virtualization that shares the host kernel.
https://<proxmox-host>:8006| Feature | LXC Container | KVM VM | | --------------- | ---------------------- | ----------------------- | | Boot time | ~1 second | ~30 seconds | | Memory overhead | Minimal | ~256 MB+ | | Kernel | Shared with host | Own kernel | | Isolation | Namespace + cgroup | Full hardware | | Performance | Near-native | Near-native (with VT-x) | | Docker support | Needs nesting | Native | | Use case | Services, Docker hosts | Windows, custom kernels |
Use LXC when: running Linux services, Docker hosts, lightweight workloads. Use KVM when: you need Windows, custom kernels, or strong isolation.
pct)# Download a template first
pveam update
pveam download local debian-12-standard_12.7-1_amd64.tar.zst
# Create an unprivileged container (most secure)
pct create 100 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname my-service \
--memory 512 \
--swap 256 \
--cores 1 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--nameserver 1.1.1.1 \
--unprivileged 1 \
--start 1
# Enter the container
pct enter 100
| Parameter | Example | Description |
| ---------------- | -------------------------------- | ---------------------------------- |
| --hostname | my-service | Container hostname |
| --memory | 2048 | RAM in MB |
| --swap | 512 | Swap in MB |
| --cores | 2 | CPU cores |
| --rootfs | local-lvm:20 | Storage and disk size (GB) |
| --net0 | name=eth0,bridge=vmbr0,ip=dhcp | Network config |
| --nameserver | 1.1.1.1 | DNS server |
| --unprivileged | 1 | Unprivileged (1) or privileged (0) |
| --features | nesting=1 | Enable container features |
| --onboot | 1 | Start on host boot |
Docker works in unprivileged containers on Proxmox 8.x+ with nesting=1 and keyctl=1 features enabled. This is the recommended approach — more secure than privileged containers.
pct create 101 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname docker-host \
--memory 4096 \
--swap 1024 \
--cores 4 \
--rootfs local-lvm:50 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--features nesting=1,keyctl=1 \
--unprivileged 1 \
--onboot 1 \
--start 1
Key settings:
--unprivileged 1 — Unprivileged container (recommended, more secure)--features nesting=1,keyctl=1 — Both required for Docker in unprivileged LXCTo enable on an existing container (or via UI: Options > Features > check nesting + keyctl):
pct set 101 --features nesting=1,keyctl=1
pct enter 101
# Install Docker
curl -fsSL https://get.docker.com | sh
# Verify
docker run --rm hello-world
# Install docker-compose plugin
apt install -y docker-compose-plugin
# Verify
docker compose version
| Aspect | Unprivileged (recommended) | Privileged |
| ----------- | ----------------------------------- | -------------------------- |
| Security | UID-mapped; escape = nobody on host | Container root = host root |
| Docker | Works with nesting=1,keyctl=1 | Works out of the box |
| Limitations | Cannot mount SMB/CIFS directly | Full host access on escape |
| ZFS caveat | Needs ext4 formatted volume | Native ZFS driver works |
Docker/containerd updates inside LXC can break with net.ipv4.ip_unprivileged_port_start permission errors. Pin or test containerd.io versions before upgrading. Snapshot before upgrading the host OS or Docker.
Lightest option — run cloudflared as a systemd service:
# Create a minimal unprivileged LXC
pct create 200 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname cloudflared \
--memory 256 \
--swap 128 \
--cores 1 \
--rootfs local-lvm:4 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--onboot 1 \
--start 1
pct enter 200
# Install cloudflared
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
| tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main" \
| tee /etc/apt/sources.list.d/cloudflared.list
apt update && apt install -y cloudflared
# Install as a service with your tunnel token
cloudflared service install <TUNNEL_TOKEN>
systemctl enable --now cloudflared
# Verify
systemctl status cloudflared
Resources: 256 MB RAM, 1 core, 4 GB disk — minimal footprint.
If you want to run multiple services (cloudflared + apps) via docker-compose:
# Create a Docker-ready LXC (see above)
pct create 201 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname docker-host \
--memory 4096 \
--cores 2 \
--rootfs local-lvm:30 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--features nesting=1,keyctl=1 \
--unprivileged 1 \
--onboot 1 \
--start 1
pct enter 201
# Install Docker
curl -fsSL https://get.docker.com | sh
# Deploy cloudflared + services via docker-compose
# See the cloudflare plugin's cloudflare-tunnels skill for compose files
# See the arcane plugin's arcane-gitops skill for GitOps deployment
# List all containers
pct list
# Start/stop/restart
pct start 100
pct stop 100
pct restart 100
# Enter container shell
pct enter 100
# Run a command inside container
pct exec 100 -- apt update
# Resize disk
pct resize 100 rootfs +10G
# Change resources (requires stop)
pct set 100 --memory 4096 --cores 4
# Snapshot
pct snapshot 100 before-upgrade
pct rollback 100 before-upgrade
# Clone
pct clone 100 102 --hostname clone-of-100
# Destroy (permanently delete)
pct destroy 100
# Backup
vzdump 100 --storage local --mode snapshot --compress zstd
# Restore
pct restore 103 /var/lib/vz/dump/vzdump-lxc-100-*.tar.zst
# CPU and memory usage
pct status 100
# Detailed status
pct config 100
# Via Proxmox API
curl -s "https://proxmox:8006/api2/json/nodes/<node>/lxc/100/status/current" \
-H "Authorization: PVEAPIToken=<user>!<token>=<secret>"
All operations available via REST API:
# Create container via API
curl -X POST "https://proxmox:8006/api2/json/nodes/<node>/lxc" \
-H "Authorization: PVEAPIToken=user@pam!mytoken=<secret>" \
-d "vmid=100" \
-d "hostname=my-container" \
-d "ostemplate=local:vztmpl/debian-12-standard.tar.zst" \
-d "memory=2048" \
-d "cores=2" \
-d "rootfs=local-lvm:20" \
-d "net0=name=eth0,bridge=vmbr0,ip=dhcp" \
-d "unprivileged=1" \
-d "start=1"
Community providers for Proxmox IaC:
// Pulumi example — create an LXC container
import * as proxmox from "@muhlba91/pulumi-proxmoxve";
const provider = new proxmox.Provider("pve", {
endpoint: "https://pve.example.com:8006/",
apiToken: "root@pam!pulumi=<secret>",
insecure: true,
});
const ct = new proxmox.ct.Container(
"docker-host",
{
nodeName: "pve",
vmId: 101,
operatingSystem: {
templateFileId: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst",
type: "debian",
},
disk: { datastoreId: "local-lvm", size: 20 },
cpu: { cores: 2 },
memory: { dedicated: 2048 },
networkInterface: { name: "eth0", bridge: "vmbr0" },
features: { nesting: true, keyctl: true },
unprivileged: true,
},
{ provider },
);
Note: Creating multiple containers in parallel can cause Proxmox lock errors. Use sequential creation or
parallelism: 1in Terraform.
| Workload | Memory | Cores | Disk | | --------------------- | ------ | ----- | ------ | | cloudflared (direct) | 256 MB | 1 | 4 GB | | Single service | 512 MB | 1 | 8 GB | | Docker host (light) | 2 GB | 2 | 20 GB | | Docker host (heavy) | 4+ GB | 4 | 50+ GB | | Database (PostgreSQL) | 2+ GB | 2 | 20+ GB |
nesting=1,keyctl=1)vmbr0): Containers get IPs on the LAN (simplest)--net0 name=eth0,bridge=vmbr0,tag=100 for network segmentation--net0 name=eth0,bridge=vmbr0,ip=10.0.0.100/24,gw=10.0.0.1tools
Manually reproduce what the github-app plugin's SessionStart hook does to make a GitHub App installation token usable in the current session — materialize the PEM, generate the token, isolate GH_CONFIG_DIR, write the runtime env file, and wire CLAUDE_ENV_FILE so every Bash call sees GH_TOKEN/GITHUB_TOKEN. Use when the hook did not run, the token is missing from the environment, or a shell/teammate needs the token wired up by hand. <example>GH_TOKEN isn't set even though github-app is configured</example> <example>the github-app SessionStart hook didn't run, set up the token manually</example> <example>wire the github app token into CLAUDE_ENV_FILE</example> <example>gh keeps falling back to the wrong account, isolate GH_CONFIG_DIR</example>
tools
Manually configure the GitHub App bot git identity the way the github-app plugin's SessionStart hook does — resolve the app slug and bot user ID, build the <slug>[bot] name and noreply email, set GIT_AUTHOR_*/GIT_COMMITTER_* env vars, and write an isolated GIT_CONFIG_GLOBAL with the gh auth git-credential helper. Use when commits are attributed to the wrong account, "Author identity unknown" appears, or git identity must be set up by hand. <example>my commits are showing up as the handler, not the bot</example> <example>git says Author identity unknown after the github-app hook ran</example> <example>configure the github app bot git identity manually</example> <example>set up the gh credential helper for git push</example>
tools
Manages spec files for requirements capture and validation
tools
# Bash Chaining Alternatives This skill teaches you how to work around the bash command chaining restriction enforced by this plugin. ## Why Chaining is Blocked The `bash-command-rejection` plugin blocks these operators: | Operator | Name | Why Blocked | | -------- | ---------- | ----------------------------------------------------------------------------------- | | `&&` | AND chain | Runs cmd2 only if cmd1 su