SecondBrain/howto/proxmox-vm-setup-with-bells...

100 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
type: howto
title: "Setting Up a New VM on a Home Proxmox Server (with the Usual Bells and Whistles)"
summary: Reusable runbook for spinning up a new VM on a home Proxmox host — scoped API token access, human-performed VM creation, pre-install snapshot, Tailscale-first access, off-host backup job, and shared host-facts documentation. Proven via the hermes01 build, reused for a Home Assistant OS VM.
tags:
- type/howto
- tool/proxmox
- tool/tailscale
- convention/infra-safety-scoping
- domain/homelab
scope: global
last_updated: 2026-07-03
update_note: experience-driven
---
# Setting Up a New VM on a Home Proxmox Server (with the Usual Bells and Whistles)
This is the generalized pattern for standing up a new VM on a home Proxmox host with the
access controls, safety net, and remote-access conventions that have proven out well.
First proven end-to-end on the `hermes01` VM (self-hosted Hermes agent, completed
2026-06-26); reused as the baseline for a second VM (Home Assistant OS) on 2026-07-03.
It's a cross-project pattern, not tied to either specific service.
This note is flagged `update_note: experience-driven` — when a new VM build finds a
discrepancy (Proxmox UI changes, a step that no longer applies, a better ordering), update
these steps rather than relying on a review date.
## The steps
1. **VM creation/import is a human-performed step, always.** Claude's Proxmox access should
be a scoped API token (e.g. `claude@pve!ops`, credentials in a local env file like
`~/.config/claude/pve.env`) allowlisted to specific VMIDs only — no host shell, and unable
to touch protected/unrelated VMs. Creating or importing a new VM, and expanding the
token's VMID allowlist to cover it, are both host-level changes that need a human at the
Proxmox UI/host shell, not something the agent executes directly.
2. **Take a pre-install snapshot before installing the primary service.** Proxmox UI → VM →
Snapshots → Take Snapshot, right after the base OS is up and before the target
application/service goes on. This gives a one-click rollback point if the install goes
sideways — cheap insurance, always worth it.
3. **Prefer Tailscale over public exposure for remote/admin access.** LAN SSH as a fallback
for local operations; never expose the VM or its services directly to the public internet.
4. **Set up a recurring, off-host backup job — not just the local snapshot.** Datacenter →
Backup → Add:
- Weekly schedule is a reasonable default cadence.
- Snapshot mode (live, no VM downtime).
- ZSTD compression.
- Storage target should be off-host (e.g. a Synology NAS backup share) — an on-host-only
backup doesn't survive a host/disk failure.
- Retention: keep-last-4 (~a month of weekly restore points) is a reasonable default.
- **Select VMs individually per backup job — never blanket-select all VMs.** This keeps
protected/unrelated VMs from being accidentally swept into a job meant for one VM.
5. **Verify the backup job actually works before trusting the schedule.** Run a one-off
manual "Run now" execution and confirm the backup lands on the target storage. Don't
assume the schedule will fire correctly untested.
6. **Document host-level facts once, shared across all VM-specific projects — don't
duplicate them per project.** Host address, VMID inventory, the token-scoping convention,
and the safety rules (never touch protected VMs, no host shell, confirm destructive
actions) belong in one place that every VM-specific project can actually see. Per-project
duplication drifts — a safety rule updated in one project's docs silently goes stale in
another's.
**Where "one place" should live, if the VM projects are siblings under an umbrella
directory**: put the shared facts directly in the umbrella's own CLAUDE.md, not in a
separate sibling directory referenced via `@../shared/CLAUDE.md`. Claude Code's `@path`
import resolver does not follow `../` references that escape a file's own directory
tree — only same-directory and subdirectory imports resolve. A shared-facts file one
level up and over from each VM project's CLAUDE.md silently never loads in a session
scoped to that project; the path renders as a dead pointer. Verified empirically by
launching a session with cwd inside a sibling project and checking which CLAUDE.md
*content* (not just referenced paths) was actually present in context.
The umbrella CLAUDE.md doesn't have this problem — it already auto-loads for any
subdirectory cwd via Claude Code's ancestor-directory walk, so inlining the shared
facts there makes them visible everywhere for free, no import needed. Per-project
CLAUDE.md files then just point to it in prose ("see the umbrella CLAUDE.md's Proxmox
Host section") rather than via `@import`. Subdirectory-descending imports (e.g. the
umbrella file importing `@some-project/docs/foo.md`) are unaffected by this and work
fine — the restriction is specifically on upward (`../`) traversal.
7. **Before defaulting to a Docker/Container install for a self-hosted service, check
whether a native/appliance-OS install path exists.** Docker-only installs sometimes lack
the supervisor/plugin/add-on ecosystem that a purpose-built appliance OS provides (e.g.
Home Assistant OS vs. Home Assistant Container, which is Core-only with no add-on store).
If the appliance-OS path exists and the missing features matter, it's usually worth the
OS being non-negotiable (can't run "Ubuntu + the app" and get the same feature set) rather
than defaulting to whatever general-purpose OS you'd otherwise pick.
## When this doesn't apply
- Purely disposable/throwaway VMs (no data, no need to survive a host failure) can skip the
backup job (steps 45) — snapshot alone is enough.
- If the Proxmox host itself is not yours to scope access to this tightly (e.g. shared
infra), the token-scoping convention in step 1 may not be available — fall back to
documenting who has host access instead.