Reusable Terraform module for provisioning cloud-init VMs on Proxmox VE with the bpg/proxmox provider.
This module is designed to be consumed as a standalone public module:
- No dependency on private/local helper modules
- All deployment-specific settings are exposed as input variables
- Provider configuration is expected in the caller (root module)
terraform {
required_version = ">= 1.0"
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.97.1"
}
}
}
provider "proxmox" {
endpoint = var.proxmox_endpoint
api_token = var.proxmox_api_token
insecure = false
}
module "vm" {
source = "<org>/proxmox-vm-linux-cloudinit/proxmox"
vm_name = "app-01"
cluster_nodes = ["pve1", "pve2"]
cloud_image_reference = {
node_name = "pve1"
datastore_id = "local"
content_type = "import"
file_name = "debian-12-genericcloud-amd64.qcow2"
}
cloud_init_mode = "builtin"
cloud_init_builtin = {
datastore_id = "local"
distro_profile = "debian"
username = "ops"
ssh_keys = ["ssh-ed25519 AAAA..."]
timezone = "UTC"
dns_domain = "example.internal"
}
disk_datastore = "local-lvm"
network_bridge = "vmbr0"
efi_disk = {
datastore_id = "local-lvm"
}
}builtin: module renders cloud-init from structured inputs (cloud_init_builtin)custom: caller passes rendered cloud-init payloads (cloud_init_custom)native: uses Proxmox native cloud-init fields only (cloud_init_native)
Supported cloud_init_builtin.distro_profile values:
genericdebianubuntufedorarhelcentos_streamrockyalmalinuxopensusearch
Chrony handling in built-in mode is profile-specific:
- Fedora/RHEL-family/OpenSUSE profiles write
/etc/chrony.d/makestep.conf - Debian/Ubuntu profiles write
/etc/chrony/conf.d/makestep.conf genericandarchprofiles addmakestep 1.0 -1to/etc/chrony.confif missing
Important: This module has been updated to support bpg/proxmox provider version 0.97.1 with expanded VM configuration options.
See MIGRATION-GUIDE.md for:
- List of variables with changed defaults
- Migration strategies for existing deployments
- Details on the new
strict_provider_defaultscompatibility toggle
Quick summary: By default, legacy module defaults are preserved (strict_provider_defaults = false). No immediate action is required for existing deployments, but explicitly setting values is recommended for production workloads.
This module now supports the full feature set of the proxmox_virtual_environment_vm resource, including:
- Advanced CPU options: architecture, flags, hotplug, NUMA, affinity
- Advanced memory options: shared memory, hugepages
- Advanced disk options: AIO mode, cache policy, speed limits, backup control
- Additional disks: attach multiple disks with per-disk configuration
- Additional network devices: multi-NIC VMs with VLAN tagging and rate limiting
- Hardware passthrough: PCIe devices, USB devices, virtiofs mappings
- Security: TPM state device, AMD SEV encryption
- System devices: audio, VGA, watchdog, RNG
- NUMA topology: explicit NUMA node configuration
- Lifecycle control: startup order, reboot behavior, protection flags
- Timeouts: per-operation timeout customization
Refer to the inputs table below for the complete list of available options.
The module supports a primary disk (disk_interface) and optional additional
disks (additional_disks). Disk interfaces are handled with the following
rules.
- Primary disk accepts
scsi,sata, orvirtiowith optional index.- Bus-only values are normalized to index
0. - Examples:
scsi->scsi0,virtio->virtio0,sata2stayssata2.
- Bus-only values are normalized to index
- Additional disks accept
ide,sata,scsi, orvirtiowith optional index.interfacemay be omitted (or set to empty string) for auto-assignment.- Bus-only values are normalized to index
0(for examplevirtio->virtio0). datastore_idis optional; when omitted, the disk inheritsdisk_datastore.
When an additional disk omits interface, the module auto-assigns the next
available index on the same bus family as the primary disk interface.
- Primary disk
virtio0with additional disks omitting interface will assignvirtio1,virtio2, and so on, skipping any already-explicitly-used indexes. - If no free index is available on that bus, planning fails with a clear error.
Before apply, the module enforces:
- Every resolved disk interface must match
ideN,sataN,scsiN, orvirtioN. - Interface names must be unique across primary and additional disks.
- Auto-assignment must have enough free indexes for all disks with omitted interfaces.
- Primary disk always uses
disk_datastore. - Additional disks use
additional_disks[*].datastore_idwhen provided. - If
additional_disks[*].datastore_idis omitted, it defaults todisk_datastore.
module "vm" {
# ...
disk_interface = "virtio0"
additional_disks = [
{
size = 50
# interface omitted -> auto-assigned (virtio1)
},
{
interface = "virtio3"
size = 100
},
{
size = 200
# interface omitted -> auto-assigned (virtio2)
}
]
}Use these defaults as a safe baseline for most deployments.
- Set the primary disk explicitly to index
0(for examplevirtio0orscsi0). - Prefer omitting
additional_disks[*].interfaceunless you need deterministic device naming. - Keep all additional disks on one bus family per VM unless you have a specific hardware/guest requirement.
- Set
disk_datastoreexplicitly and only override per-diskdatastore_idwhen required. - Use explicit disk sizes and avoid relying on implied defaults in production.
module "vm" {
# ...
disk_datastore = "local-lvm"
disk_interface = "virtio0"
disk_size_gb = 40
disk_discard = "on"
disk_iothread = true
disk_ssd = true
additional_disks = [
{
size = 100
},
{
size = 200
}
]
}Set explicit additional_disks[*].interface only when you need stable,
predefined device addresses for guest-level automation, monitoring, or strict
OS mount mapping expectations.
- Setting primary disk to a non-zero index without a clear reason.
- Manually assigning additional disk indexes that collide with primary or other additional disks.
- Mixing auto-assigned and manually assigned interfaces without checking index gaps and intent.
- Using bus-only values expecting non-zero indexes (for example
virtioalways normalizes tovirtio0).
This repository uses terraform-docs to keep variable and output references accurate.
- Config file:
.terraform-docs.yml - Injection markers are present near the end of this README
Preferred commands from repository root:
make docs
make docs-checkEquivalent script commands:
./scripts/terraform-docs-generate.sh
./scripts/terraform-docs-check.shUnderlying Podman invocation:
podman run --rm \
-v "$PWD:/terraform-docs:Z" \
-w /terraform-docs \
quay.io/terraform-docs/terraform-docs:latest \
markdown table --config /terraform-docs/.terraform-docs.yml .| Name | Version |
|---|---|
| terraform | >= 1.0 |
| proxmox | ~> 0.97.1 |
| Name | Version |
|---|---|
| proxmox | ~> 0.97.1 |
| random | n/a |
No modules.
| Name | Type |
|---|---|
| proxmox_virtual_environment_file.builtin_user_data | resource |
| proxmox_virtual_environment_file.meta_data | resource |
| proxmox_virtual_environment_file.network_data | resource |
| proxmox_virtual_environment_file.user_data | resource |
| proxmox_virtual_environment_file.vendor_data | resource |
| proxmox_virtual_environment_vm.vm | resource |
| random_shuffle.node_selection | resource |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| acpi | Whether to enable ACPI | bool |
true |
no |
| additional_disks | Additional disk blocks | list(object({ |
[] |
no |
| additional_network_devices | Additional network_device blocks | list(object({ |
[] |
no |
| agent_enabled | Enable QEMU guest agent | bool |
null |
no |
| agent_timeout | How long to wait for QEMU guest agent during create/update | string |
null |
no |
| agent_trim | Enable QEMU agent FSTRIM | bool |
false |
no |
| agent_type | QEMU agent interface type | string |
"virtio" |
no |
| agent_wait_for_ipv4 | Wait for at least one non-link-local IPv4 address from guest agent | bool |
false |
no |
| agent_wait_for_ipv6 | Wait for at least one non-link-local IPv6 address from guest agent | bool |
false |
no |
| amd_sev | Optional AMD SEV configuration | object({ |
null |
no |
| assigned_memory | Assigned memory in MB | number |
null |
no |
| audio_device | Optional audio device configuration | object({ |
null |
no |
| auto_dhcp_on_builtin_custom | Automatically set IPv4 DHCP in Proxmox ip_config for builtin mode and for custom mode when network_data is not supplied | bool |
true |
no |
| bios_type | BIOS type (seabios or ovmf) | string |
null |
no |
| boot_order | Optional explicit boot order list (for example ["scsi0", "ide2"]) | list(string) |
null |
no |
| cdrom | Optional CD-ROM configuration | object({ |
null |
no |
| clone | Optional clone configuration | object({ |
null |
no |
| cloud_image_lookup_enabled | Whether to resolve cloud image by filename on each plan/apply. Set to false after initial create to avoid lookup failures when image files are later removed from datastore. | bool |
true |
no |
| cloud_image_reference | Reference to cloud image file for cloning | object({ |
n/a | yes |
| cloud_init_builtin | Built-in standard cloud-init configuration rendered by the module. Use with cloud_init_mode = 'builtin'. | object({ |
null |
no |
| cloud_init_custom | Custom cloud-init data files | object({ |
null |
no |
| cloud_init_interface | Interface for cloud-init drive (defaults based on BIOS type: sata0 for OVMF, ide2 for SeaBIOS) | string |
null |
no |
| cloud_init_mode | Cloud-init mode: 'native' for Proxmox native, 'custom' for caller-rendered YAML, 'builtin' for module standard template | string |
"custom" |
no |
| cloud_init_native | Native Proxmox cloud-init configuration | object({ |
null |
no |
| cluster_nodes | List of cluster nodes for random selection (required if node_name is null) | list(string) |
[] |
no |
| cpu_additional | Additional CPU options | object({ |
null |
no |
| cpu_cores | Number of CPU cores | number |
null |
no |
| cpu_type | CPU type | string |
null |
no |
| delete_unreferenced_disks_on_destroy | Delete unreferenced disks on destroy | bool |
true |
no |
| description | VM description | string |
"" |
no |
| disk_advanced | Advanced settings for the primary disk | object({ |
null |
no |
| disk_datastore | Datastore for VM disks | string |
n/a | yes |
| disk_discard | Enable discard/TRIM | string |
null |
no |
| disk_interface | Primary disk interface (indexed form like scsi0/virtio1, or bus-only scsi/sata/virtio which normalizes to index 0) | string |
"scsi0" |
no |
| disk_iothread | Enable iothread | bool |
null |
no |
| disk_size_gb | Disk size in GB | number |
20 |
no |
| disk_ssd | Mark disk as SSD | bool |
null |
no |
| efi_disk | EFI disk configuration for OVMF BIOS | object({ |
null |
no |
| hook_script_file_id | Optional hook script file ID | string |
null |
no |
| hostname | Hostname for cloud-init (defaults to vm_name if not specified) | string |
null |
no |
| hostpci | Optional host PCI passthrough mappings | list(object({ |
[] |
no |
| hotplug | Hotplug feature string (for example 'network,disk,usb', '1', or '0') | string |
null |
no |
| initialization_advanced | Advanced initialization block controls | object({ |
null |
no |
| keyboard_layout | Keyboard layout | string |
"en-us" |
no |
| kvm_arguments | Arbitrary arguments passed to KVM | string |
null |
no |
| machine | VM machine type | string |
"pc" |
no |
| memory_additional | Additional memory options | object({ |
null |
no |
| migrate | Allow VM migration | bool |
null |
no |
| minimum_memory | Minimum memory in MB (defaults to assigned_memory when null) | number |
null |
no |
| network_advanced | Advanced settings for the primary network device | object({ |
null |
no |
| network_bridge | Network bridge | string |
n/a | yes |
| network_model | Network device model | string |
"virtio" |
no |
| node_name | Proxmox node to deploy to (null for random selection from cluster) | string |
null |
no |
| numa | Optional NUMA topology configuration | list(object({ |
[] |
no |
| on_boot | Start VM on host boot | bool |
null |
no |
| os_type | Operating system type | string |
"l26" |
no |
| pool_id | Optional Proxmox pool ID to assign the VM | string |
null |
no |
| protection | Sets the protection flag for the VM and disables the remove VM and remove disk operations | bool |
false |
no |
| purge_on_destroy | Purge VM from backup jobs on destroy | bool |
true |
no |
| reboot | Reboot VM after initial creation | bool |
false |
no |
| reboot_after_update | Reboot VM after update if needed | bool |
true |
no |
| rng | Optional random number generator configuration | object({ |
null |
no |
| scsi_hardware | SCSI hardware type | string |
"virtio-scsi-pci" |
no |
| serial_devices | Serial devices. Empty list keeps a single default socket serial device for backwards compatibility. | list(object({ |
[] |
no |
| smbios | Optional SMBIOS type1 configuration | object({ |
null |
no |
| started | Whether to start VM | bool |
true |
no |
| startup | Optional startup/shutdown behavior | object({ |
null |
no |
| stop_on_destroy | Stop VM on destroy instead of shutdown | bool |
null |
no |
| strict_provider_defaults | When true, unset module inputs resolve to documented bpg/proxmox provider defaults instead of legacy module defaults | bool |
false |
no |
| tablet_device | Enable USB tablet device | bool |
true |
no |
| tags | VM tags | list(string) |
[] |
no |
| tags_environment | Environment tags merged before tags | list(string) |
[] |
no |
| tags_global | Global tags merged before tags | list(string) |
[ |
no |
| tags_instance | Instance-specific tags merged before tags | list(string) |
[] |
no |
| tags_role | Role tags merged before tags | list(string) |
[] |
no |
| template | Whether to create as VM template | bool |
false |
no |
| timeout_clone | Timeout for cloning VM in seconds | number |
1800 |
no |
| timeout_create | Timeout for creating VM in seconds | number |
1800 |
no |
| timeout_migrate | Timeout for migrating VM in seconds | number |
1800 |
no |
| timeout_reboot | Timeout for rebooting VM in seconds | number |
1800 |
no |
| timeout_shutdown_vm | Timeout for shutting down VM in seconds | number |
1800 |
no |
| timeout_start_vm | Timeout for starting VM in seconds | number |
1800 |
no |
| timeout_stop_vm | Timeout for stopping VM in seconds | number |
300 |
no |
| tpm_state | Optional TPM state device | object({ |
null |
no |
| usb | Optional USB passthrough mappings | list(object({ |
[] |
no |
| vga | Optional VGA configuration | object({ |
null |
no |
| virtiofs | Optional virtiofs mappings | list(object({ |
[] |
no |
| vm_id | VM ID (null for auto-assignment) | number |
null |
no |
| vm_name | Name of the VM in Proxmox (will be used as default hostname if hostname not specified) | string |
n/a | yes |
| watchdog | Optional watchdog configuration | object({ |
null |
no |
| Name | Description |
|---|---|
| ipv4_addresses | IPv4 addresses of the VM |
| ipv6_addresses | IPv6 addresses of the VM |
| mac_addresses | MAC addresses of the VM |
| node_name | The Proxmox node where the VM is deployed |
| vm_id | The ID of the created VM |
| vm_name | The name of the created VM |