Files
2025-11-29 18:00:27 +08:00
..
2025-11-29 18:00:27 +08:00
2025-11-29 18:00:27 +08:00
2025-11-29 18:00:27 +08:00

Basic VM Deployment Example

Learning objective: Deploy your first VM using the unified VM module with minimal configuration.

What This Example Shows

  • Minimal required configuration for VM deployment
  • Cloning from an existing template
  • Static IP address configuration with cloud-init
  • SSH key injection
  • Module defaults (what you DON'T need to specify)

Prerequisites

  1. Proxmox template exists (VMID 9000)

    • Create one using: terraform/netbox-template/ or Ansible playbook
    • Or use Triangulum-Prime template examples
  2. Proxmox API credentials configured:

    export PROXMOX_VE_ENDPOINT="https://192.168.3.5:8006"
    export PROXMOX_VE_API_TOKEN="user@realm!token-id=secret"
    # OR
    export PROXMOX_VE_USERNAME="root@pam"
    export PROXMOX_VE_PASSWORD="your-password"
    
  3. SSH public key available:

    export TF_VAR_ssh_public_key="$(cat ~/.ssh/id_rsa.pub)"
    

Quick Start

1. Initialize Terraform

tofu init

2. Review the Plan

tofu plan

Expected resources:

  • 1 VM (cloned from template 9000)
  • Cloud-init configuration
  • Network interface with static IP

3. Deploy

tofu apply

4. Verify

# SSH into the VM
ssh ansible@192.168.3.100

# Check VM in Proxmox
qm status 100  # Or whatever VMID was assigned

5. Cleanup

tofu destroy

Understanding the Configuration

What You MUST Specify

# These 6 parameters are required:
vm_type       = "clone"               # Clone from template
pve_node      = "foxtrot"             # Which node
vm_name       = "test-vm-01"          # VM name
src_clone     = { ... }               # Template to clone
vm_disk       = { ... }               # Disk config
vm_net_ifaces = { ... }               # Network config
vm_init       = { ... }               # Cloud-init config
vm_efi_disk   = { ... }               # EFI boot disk

What Uses Defaults

The module provides sensible defaults for:

Setting Default Why It's Good
CPU cores 2 Minimal baseline
Memory 2048 MB (2GB) Enough for most services
CPU type host Best performance
Guest agent Enabled Needed for IP detection
BIOS ovmf (UEFI) Modern, secure
Machine q35 Modern chipset
Display Standard VGA Works everywhere
Serial console Enabled Troubleshooting
RNG device Enabled Entropy for crypto

See: Module DEFAULTS.md

Customization

Change VM Resources

Override defaults in main.tf:

module "basic_vm" {
  # ... required params ...

  # Override CPU
  vm_cpu = {
    cores = 4  # Increase to 4 cores
  }

  # Override memory
  vm_mem = {
    dedicated = 8192  # 8GB
  }
}

Use Different Template

Change the template ID:

src_clone = {
  datastore_id = "local-lvm"
  tpl_id       = 9001  # Different template
}

Add VLAN Tagging

vm_net_ifaces = {
  net0 = {
    bridge    = "vmbr0"
    vlan_id   = 30         # Add VLAN tag
    ipv4_addr = "192.168.3.100/24"
    ipv4_gw   = "192.168.3.1"
  }
}

Common Issues

Issue: "Template 9000 not found"

Solution: Create a template first:

cd ../../.. # Back to repo root
cd terraform/netbox-template
tofu apply

Issue: "IP address already in use"

Solution: Change ip_address variable:

tofu apply -var="ip_address=192.168.3.101"

Issue: "Cannot connect to Proxmox API"

Solution: Check credentials:

echo $PROXMOX_VE_ENDPOINT
echo $PROXMOX_VE_API_TOKEN

Issue: "EFI disk creation failed"

Solution: Ensure datastore has space:

# On Proxmox node
pvesm status

Next Steps

Learn More

  1. Production Configuration: See ../02-production-vm/

    • Shows common overrides for production
    • Resource sizing best practices
    • Tagging and organization
  2. Template Creation: See ../03-template-creation/

    • How to create templates from cloud images
    • Template best practices
  3. Complete Examples: Triangulum-Prime repository

Integration Examples

  • NetBox + DNS: See .claude/skills/netbox-powerdns-integration/examples/01-vm-with-dns/
  • Ansible Configuration: See .claude/skills/ansible-best-practices/examples/

Module Documentation

Philosophy: DRY (Don't Repeat Yourself)

This example follows the module's DRY principle:

Good: Only specify what differs from defaults

vm_cpu = {
  cores = 4  # Only override cores, use default type
}

Bad: Repeating module defaults

vm_cpu = {
  cores = 4
  type  = "host"  # This is already the default!
}

Why? Reduces maintenance burden and makes changes obvious.