Skip to content

Proxmox SDN with Terraform

Module: hybridops-tech/sdn/proxmox · GitHub: terraform-proxmox-sdn

Prerequisites: Proxmox VE 8.x with SDN enabled · dnsmasq installed · Linux control node with Terraform (and Terragrunt if using the stack workflow).


What the module manages

Layer What gets provisioned
Proxmox SDN Zone, VNets, subnets
Host networking L3 gateways on vnet* interfaces
Egress SNAT via uplink bridge
DHCP dnsmasq units per subnet

Three toggles control host-level behaviour:

enable_host_l3 = true   # gateway IPs on vnet* interfaces
enable_snat    = true   # SNAT out of zone_bridge
enable_dhcp    = true   # dnsmasq DHCP (requires enable_host_l3 = true)

1. Proxmox API token

In the Proxmox UI, create an API token (automation@pam!infra-token) with SDN, node networking, and node-read permissions. Then export on your control node:

export PROXMOX_URL="https://<PROXMOX-IP>:8006/api2/json"
export PROXMOX_TOKEN_ID="automation@pam!infra-token"
export PROXMOX_TOKEN_SECRET="<UUID>"
export PROXMOX_NODE="<NODE-NAME>"

2. Option A: Standalone Terraform

mkdir -p ~/proxmox-sdn && cd ~/proxmox-sdn
touch main.tf variables.tf terraform.tfvars

main.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = ">= 0.50.0"
    }
  }
}

provider "proxmox" {
  endpoint  = var.proxmox_url
  api_token = var.proxmox_token
  insecure  = var.proxmox_insecure
}

module "sdn" {
  source  = "hybridops-tech/sdn/proxmox"
  version = "~> 0.1.5"

  zone_name    = "hybzone"
  zone_bridge  = "vmbr0"
  proxmox_node = var.proxmox_node
  proxmox_host = var.proxmox_host

  enable_host_l3 = true
  enable_snat    = true
  enable_dhcp    = true

  dns_domain = "hybridops.local"
  dns_lease  = "24h"

  vnets = {
    vnetmgmt = {
      vlan_id     = 10
      description = "Management network"
      subnets = {
        mgmt = {
          cidr             = "10.10.0.0/24"
          gateway          = "10.10.0.1"
          dhcp_range_start = "10.10.0.100"
          dhcp_range_end   = "10.10.0.200"
          dhcp_dns_server  = "8.8.8.8"
        }
      }
    }
  }

  proxmox_url      = var.proxmox_url
  proxmox_token    = var.proxmox_token
  proxmox_insecure = var.proxmox_insecure
}

terraform.tfvars

proxmox_url      = "https://<PROXMOX-IP>:8006/api2/json"
proxmox_token    = "root@pam!terraform=<YOUR-API-TOKEN-SECRET>"
proxmox_insecure = true
proxmox_node     = "<NODE-NAME>"
proxmox_host     = "<PROXMOX-IP>"
terraform init && terraform plan && terraform apply

3. Option B: Terragrunt (HybridOps stack)

In network-sdn/terragrunt.hcl:

include "root" {
  path = find_in_parent_folders("root.hcl")
}

terraform {
  source = "tfr://registry.terraform.io/hybridops-tech/sdn/proxmox?version=0.1.5"
}

locals {
  proxmox_url      = local.env.PROXMOX_URL
  proxmox_token    = "${local.env.PROXMOX_TOKEN_ID}=${local.env.PROXMOX_TOKEN_SECRET}"
  proxmox_insecure = true
  proxmox_node     = local.env.PROXMOX_NODE
  proxmox_host     = split(":", local.env.PROXMOX_HOST)[0]
}

inputs = {
  zone_name    = "hybzone"
  zone_bridge  = "vmbr0"
  proxmox_node = local.proxmox_node
  proxmox_host = local.proxmox_host

  enable_host_l3 = true
  enable_snat    = true
  enable_dhcp    = true

  dns_domain = "hybridops.local"
  dns_lease  = "24h"

  vnets = {
    vnetmgmt = { vlan_id = 10, description = "Management", subnets = { submgmt = { cidr = "10.10.0.0/24", gateway = "10.10.0.1", dhcp_range_start = "10.10.0.120", dhcp_range_end = "10.10.0.220", dhcp_dns_server = "8.8.8.8" } } }
    vnetobs  = { vlan_id = 11, description = "Observability", subnets = { subobs = { cidr = "10.11.0.0/24", gateway = "10.11.0.1", dhcp_range_start = "10.11.0.120", dhcp_range_end = "10.11.0.220", dhcp_dns_server = "8.8.8.8" } } }
    vnetdev  = { vlan_id = 20, description = "Development",   subnets = { subdev = { cidr = "10.20.0.0/24", gateway = "10.20.0.1" } } }
    vnetprod = { vlan_id = 40, description = "Production",    subnets = { subprod = { cidr = "10.40.0.0/24", gateway = "10.40.0.1" } } }
  }

  proxmox_url      = local.proxmox_url
  proxmox_token    = local.proxmox_token
  proxmox_insecure = local.proxmox_insecure
}
cd network-sdn && terragrunt init && terragrunt plan && terragrunt apply

4. Verify

# SDN objects
ssh root@<PROXMOX_HOST> 'pvesh get /cluster/sdn/zones'
ssh root@<PROXMOX_HOST> 'pvesh get /cluster/sdn/vnets'

# Host networking
ssh root@<PROXMOX_HOST> 'ip addr show | grep "inet 10\."'

# DHCP units
ssh root@<PROXMOX_HOST> 'systemctl list-units "dnsmasq@hybridops-sdn-dhcp-*" --no-pager'

5. Naming constraints

Proxmox SDN enforces: IDs ≤ 8 characters, lowercase, no dashes.

  • Valid: hybzone, vnetmgmt, vclst01m
  • Invalid: basic-zone, vnet-mgmt

6. Key outputs

Output Description
zone_name SDN zone ID
vnets Map of VNet keys → id, zone, vlan_id
subnets Map of <vnet>-<subnet> → CIDR, gateway, DHCP config

References