---
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 4–5) — 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.
