claude-home/legacy/headless-claude/docs/lxc-setup-guide.md
Cal Corum babf062d6a docs: archive headless-claude design docs to legacy/
Original planning folder (no git repo) for the server diagnostics system
that runs on CT 300. Live deployment is on claude-runner; this preserves
the Agent SDK reference, PRD with Phase 2/3 roadmap, and N8N workflow designs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 08:15:13 -06:00

11 KiB

Claude Code LXC Setup Guide

This guide walks through setting up a dedicated LXC container on Proxmox for running Claude Code in headless mode.

Prerequisites

  • Proxmox VE host with available resources
  • Ubuntu/Debian LXC template
  • Anthropic API key (from console.anthropic.com)
  • SSH access to Proxmox host

1. Create LXC Container

Via Proxmox Web UI

  1. Navigate to your Proxmox node

  2. Click Create CT

  3. Configure:

    • CT ID: 300 (or next available)
    • Hostname: claude-code
    • Password: Set a secure password
    • SSH Public Key: Add your key for access
  4. Template:

    • Template: ubuntu-22.04-standard or debian-12-standard
  5. Resources:

    • Disk: 16 GB (local-lvm)
    • CPU: 2 cores
    • Memory: 2048 MB
    • Swap: 512 MB
  6. Network:

    • Bridge: vmbr0
    • IPv4: DHCP or static (e.g., 10.10.0.50/24)
    • Gateway: Your network gateway
  7. DNS:

    • Use host settings or configure manually
  8. Click Finish and start the container

Via CLI (pct)

# SSH to Proxmox host
ssh root@proxmox-host

# Create container
pct create 300 local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
  --hostname claude-code \
  --memory 2048 \
  --swap 512 \
  --cores 2 \
  --rootfs local-lvm:16 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --unprivileged 1 \
  --features nesting=1 \
  --start 1

# Set password
pct exec 300 -- passwd root

2. Initial Container Setup

# Enter container
pct enter 300
# Or SSH: ssh root@10.10.0.50

# Update system
apt update && apt upgrade -y

# Install essential packages
apt install -y \
  curl \
  wget \
  git \
  openssh-client \
  python3 \
  python3-pip \
  python3-venv \
  ca-certificates \
  gnupg \
  jq \
  vim

# Install Node.js 20.x (required for Claude Code)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs

# Verify installations
node --version   # Should be v20.x
npm --version
python3 --version

3. Install Claude Code CLI

# Install Claude Code globally
npm install -g @anthropic-ai/claude-code

# Verify installation
claude --version

4. Configure Authentication (Max Subscription)

Using your Claude Max subscription is the recommended approach - no API key management needed, and you get the generous Max tier rate limits included in your $20/month subscription.

Authenticate with Max Subscription

# Run Claude Code interactively
claude

# You'll see a device code prompt:
# ┌────────────────────────────────────────────────────┐
# │  To authenticate, visit:                           │
# │  https://console.anthropic.com/device              │
# │                                                    │
# │  Enter code: ABCD-1234                             │
# └────────────────────────────────────────────────────┘

# 1. Open the URL in your browser (on any device)
# 2. Log in with your Anthropic account (Max subscription)
# 3. Enter the code shown in the terminal
# 4. Claude Code will confirm authentication

Credentials are stored in ~/.claude/ and persist across sessions.

Verify Authentication

# Test headless mode
claude -p "Say hello" --output-format json

# Should return JSON with response like:
# {"type":"result","result":"Hello! How can I help you today?","session_id":"..."}

Token Refresh

OAuth tokens may expire after weeks/months. If headless mode starts failing with authentication errors:

  1. SSH into the LXC
  2. Run claude interactively
  3. Re-authenticate via device code flow

Consider adding a health check in N8N to detect auth failures and alert you.

5. Set Up SSH Keys for Server Access

# Create .ssh directory
mkdir -p ~/.ssh
chmod 700 ~/.ssh

# Generate dedicated key pair for diagnostics
ssh-keygen -t ed25519 -f ~/.ssh/claude_diagnostics_key -N "" -C "claude-code-diagnostics"

# View public key (copy this)
cat ~/.ssh/claude_diagnostics_key.pub

Install Key on Target Servers

# On Proxmox host (or other target servers)
# Add the public key to authorized_keys
echo "ssh-ed25519 AAAA... claude-code-diagnostics" >> ~/.ssh/authorized_keys

# Or use ssh-copy-id from Claude Code LXC
ssh-copy-id -i ~/.ssh/claude_diagnostics_key.pub root@10.10.0.11

Test SSH Connection

# From Claude Code LXC
ssh -i ~/.ssh/claude_diagnostics_key root@10.10.0.11 "hostname && docker ps"

# Should show hostname and Docker containers

6. Install Python Dependencies

# Install uv (fast Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc

# Create virtual environment for skills
mkdir -p ~/.claude/skills
cd ~/.claude/skills

# Install skill dependencies
uv venv
source .venv/bin/activate
uv pip install paramiko pyyaml

7. Install Server Diagnostics Skill

# Create skill directory
mkdir -p ~/.claude/skills/server-diagnostics

# Copy skill files (from development machine)
# Or clone from repo when available

Create config.yaml

cat > ~/.claude/skills/server-diagnostics/config.yaml << 'EOF'
# Server Diagnostics Configuration

servers:
  proxmox-host:
    hostname: 10.10.0.11  # Update with actual IP
    ssh_user: root
    ssh_key: ~/.ssh/claude_diagnostics_key
    description: "Main Proxmox host running Docker services"

docker_containers:
  - name: tdarr
    critical: true
    restart_allowed: true
  - name: portainer
    critical: true
    restart_allowed: true
  - name: n8n
    critical: true
    restart_allowed: false
  - name: plex
    critical: true
    restart_allowed: true

diagnostic_commands:
  disk_usage: "df -h"
  memory_usage: "free -h"
  cpu_usage: "top -bn1 | head -20"
  process_list: "ps aux --sort=-%mem | head -20"
  network_status: "ss -tuln"
  docker_ps: "docker ps -a --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'"

remediation_commands:
  docker_restart: "docker restart {container}"
  docker_logs: "docker logs --tail 500 {container}"

denied_patterns:
  - "rm -rf"
  - "dd if="
  - "mkfs"
  - "shutdown"
  - "reboot"
  - "> /dev/sd"
EOF

8. Configure Claude Code settings.json

mkdir -p ~/.claude

cat > ~/.claude/settings.json << 'EOF'
{
  "permissions": {
    "allow": [
      "Bash(ssh root@10.10.0.11 docker:*)",
      "Bash(ssh root@10.10.0.11 systemctl status:*)",
      "Bash(ssh root@10.10.0.11 journalctl:*)",
      "Bash(ssh root@10.10.0.11 df:*)",
      "Bash(ssh root@10.10.0.11 free:*)",
      "Bash(ssh root@10.10.0.11 ps:*)",
      "Bash(ssh root@10.10.0.11 top:*)",
      "Bash(ssh root@10.10.0.11 ss:*)",
      "Bash(python3 ~/.claude/skills/server-diagnostics/client.py:*)"
    ],
    "deny": [
      "Bash(rm -rf:*)",
      "Bash(dd:*)",
      "Bash(mkfs:*)",
      "Bash(shutdown:*)",
      "Bash(reboot:*)",
      "Bash(*> /dev/sd*)"
    ]
  },
  "model": "sonnet"
}
EOF

9. Configure SSH for Known Hosts

# Pre-add Proxmox host to known hosts to avoid prompts
ssh-keyscan -H 10.10.0.11 >> ~/.ssh/known_hosts

10. Create Test Script

cat > ~/test-claude-headless.sh << 'EOF'
#!/bin/bash
# Test Claude Code headless mode

echo "Testing Claude Code headless mode..."

# Simple test
result=$(claude -p "What is 2+2? Reply with just the number." --output-format json 2>&1)

if echo "$result" | jq -e '.result' > /dev/null 2>&1; then
    echo "✅ Claude Code headless mode working"
    echo "Response: $(echo "$result" | jq -r '.result')"
elif echo "$result" | grep -q "authenticate"; then
    echo "❌ Authentication required - run 'claude' interactively to log in"
    exit 1
else
    echo "❌ Claude Code headless mode failed"
    echo "Output: $result"
    exit 1
fi

# Test SSH access
echo ""
echo "Testing SSH to Proxmox host..."
if ssh -i ~/.ssh/claude_diagnostics_key -o ConnectTimeout=5 root@10.10.0.11 "echo 'SSH OK'" 2>/dev/null; then
    echo "✅ SSH connection working"
else
    echo "❌ SSH connection failed"
    exit 1
fi

# Test Docker access
echo ""
echo "Testing Docker access on Proxmox host..."
containers=$(ssh -i ~/.ssh/claude_diagnostics_key root@10.10.0.11 "docker ps --format '{{.Names}}'" 2>/dev/null)
if [ -n "$containers" ]; then
    echo "✅ Docker access working"
    echo "Containers found:"
    echo "$containers" | sed 's/^/  - /'
else
    echo "⚠️  No containers found or Docker not accessible"
fi

echo ""
echo "All tests completed!"
EOF

chmod +x ~/test-claude-headless.sh

11. Run Verification Tests

# Run the test script
~/test-claude-headless.sh

Expected output:

Testing Claude Code headless mode...
✅ Claude Code headless mode working
Response: 4

Testing SSH to Proxmox host...
✅ SSH connection working

Testing Docker access on Proxmox host...
✅ Docker access working
Containers found:
  - tdarr
  - portainer
  - n8n
  - plex

All tests completed!

12. Configure N8N Access (Next Step)

The N8N container needs to be able to invoke Claude Code on this LXC. Options:

Option A: SSH from N8N to Claude LXC

# On N8N container, generate key and copy to Claude LXC
ssh-keygen -t ed25519 -f ~/.ssh/claude_lxc_key -N ""
ssh-copy-id -i ~/.ssh/claude_lxc_key.pub root@10.10.0.50

# N8N Execute Command will use:
# ssh -i ~/.ssh/claude_lxc_key root@10.10.0.50 "claude -p '...' --output-format json"

Option B: Local Execution (if N8N runs on same host)

If N8N runs in a container on the Proxmox host, you can mount the Claude LXC filesystem or use pct exec:

# From Proxmox host
pct exec 300 -- claude -p "..." --output-format json

Troubleshooting

Claude Code Not Found

# Check npm global path
npm config get prefix

# Ensure it's in PATH
export PATH="$PATH:$(npm config get prefix)/bin"

SSH Permission Denied

# Check key permissions
chmod 600 ~/.ssh/claude_diagnostics_key
chmod 644 ~/.ssh/claude_diagnostics_key.pub

# Check authorized_keys on target
ssh root@10.10.0.11 "cat ~/.ssh/authorized_keys"

Authentication Expired

# If headless mode returns auth errors, re-authenticate:
claude

# Follow the device code flow to log in again
# Credentials will be refreshed in ~/.claude/

Container Network Issues

# Check network configuration
ip addr
ping -c 3 google.com

# If no connectivity, check Proxmox network settings

Security Considerations

  1. OAuth Credentials: Authentication tokens are stored in ~/.claude/. Ensure the LXC has restricted access and backups don't expose this directory.

  2. SSH Key Scope: The claude_diagnostics_key should only be installed on servers that need automated diagnostics.

  3. Minimal Permissions: The SSH key on target servers could use command= restrictions for additional security (Phase 2 enhancement).

  4. Network Isolation: Consider placing the Claude LXC on an internal-only network segment.

  5. Session Security: Your Max subscription is tied to this LXC. Don't share access to the container.

Snapshot Before Production

# On Proxmox host, create snapshot
pct snapshot 300 before-production --description "Claude Code LXC fully configured"

Next Steps

  1. LXC created and configured
  2. Claude Code installed and authenticated
  3. SSH keys installed on target servers
  4. Install server-diagnostics skill (when code ready)
  5. Configure N8N workflow
  6. Test end-to-end pipeline