Automation advanced ⏱ 60 minutes

Terraform for Homelabs: Infrastructure as Code

Manage your homelab with Terraform. Providers for Proxmox, Docker, and cloud services.

By homelabbing.io • 2025-12-17
#terraform#iac#infrastructure#proxmox#automation

Terraform transforms homelab management from manual clicking and scripting into declarative, version-controlled infrastructure as code (IaC). This guide covers comprehensive Terraform usage for Proxmox, including VMs, LXC containers, networking, storage, and advanced patterns for production-ready homelab automation.

Understanding Infrastructure as Code

What is Infrastructure as Code?

Infrastructure as Code treats your infrastructure configuration as software code:

  • Declarative: Describe desired state, not steps to achieve it
  • Version Controlled: Track changes with Git
  • Repeatable: Rebuild infrastructure consistently
  • Testable: Validate before applying
  • Documented: Code serves as documentation

Why Terraform for Homelabs?

Benefits:

  • Manage Proxmox VMs and containers programmatically
  • Version control your entire homelab configuration
  • Rebuild infrastructure quickly after failures
  • Test changes before applying
  • Share and reuse configurations
  • Integrate with CI/CD pipelines

Alternatives:

  • Ansible: Better for configuration management, not infrastructure provisioning
  • Pulumi: Uses programming languages instead of HCL
  • CloudFormation: AWS-specific
  • Manual CLI/API: Error-prone, not repeatable

Part 1: Installation and Setup

Prerequisites

# Update system
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install -y wget unzip git

Install Terraform

Method 1: Official HashiCorp Repository (Recommended)

# Add HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

# Add repository
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Install Terraform
sudo apt update && sudo apt install terraform

# Verify installation
terraform version

Method 2: Binary Installation

# Download latest version (check https://www.terraform.io/downloads)
cd /tmp
wget https://releases.hashicorp.com/terraform/1.6.6/terraform_1.6.6_linux_amd64.zip

# Extract and install
unzip terraform_1.6.6_linux_amd64.zip
sudo mv terraform /usr/local/bin/

# Verify
terraform version

Configure Terraform Shell Completion

# Bash
terraform -install-autocomplete

# Or manually
cat >> ~/.bashrc << 'EOF'
complete -C /usr/local/bin/terraform terraform
EOF

source ~/.bashrc
# Install terraform-docs for documentation generation
cd /tmp
curl -Lo terraform-docs.tar.gz https://github.com/terraform-docs/terraform-docs/releases/download/v0.17.0/terraform-docs-v0.17.0-linux-amd64.tar.gz
tar -xzf terraform-docs.tar.gz
sudo mv terraform-docs /usr/local/bin/
terraform-docs --version

Part 2: Proxmox Provider Setup

Proxmox API User Creation

Terraform needs API access to Proxmox:

# SSH into Proxmox host
ssh root@YOUR-PROXMOX-IP

# Create Terraform user
pveum user add terraform@pve --password YOUR-SECURE-PASSWORD

# Create Terraform role with necessary permissions
pveum role add TerraformRole -privs "VM.Allocate VM.Clone VM.Config.CDROM VM.Config.CPU VM.Config.Cloudinit VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.Audit VM.PowerMgmt Datastore.AllocateSpace Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify SDN.Use"

# Assign role to user
pveum aclmod / -user terraform@pve -role TerraformRole

# Verify
pveum user list

Alternative: API Token (More Secure)

# Create API token
pveum user token add terraform@pve terraform-token --privsep=0

# Output will show: full-tokenid=terraform@pve!terraform-token
# Save the secret shown - you cannot retrieve it later

# Assign permissions to token
pveum aclmod / -token terraform@pve!terraform-token -role TerraformRole

Project Structure

# Create Terraform project directory
mkdir -p ~/terraform-homelab
cd ~/terraform-homelab

# Recommended structure:
mkdir -p {modules,environments,templates}

# Directory structure:
# terraform-homelab/
# ├── main.tf                 # Main configuration
# ├── variables.tf            # Variable declarations
# ├── outputs.tf              # Output declarations
# ├── terraform.tfvars        # Variable values (add to .gitignore)
# ├── versions.tf             # Provider version constraints
# ├── modules/                # Reusable modules
# │   ├── proxmox-vm/
# │   ├── proxmox-lxc/
# │   └── network/
# ├── environments/           # Environment-specific configs
# │   ├── prod/
# │   ├── dev/
# │   └── test/
# └── templates/              # Cloud-init, scripts
#     ├── cloud-init/
#     └── scripts/

Part 3: Basic Terraform Configuration

versions.tf - Provider Configuration

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "~> 2.9.14"
    }
  }

  # Optional: Remote state storage
  # backend "s3" {
  #   bucket = "terraform-state"
  #   key    = "homelab/terraform.tfstate"
  #   region = "us-east-1"
  # }
}

provider "proxmox" {
  pm_api_url          = var.proxmox_api_url
  pm_api_token_id     = var.proxmox_api_token_id
  pm_api_token_secret = var.proxmox_api_token_secret
  pm_tls_insecure     = true  # Set to false in production with valid certs

  # Alternative: Username/password authentication
  # pm_user     = var.proxmox_user
  # pm_password = var.proxmox_password

  pm_log_enable = true
  pm_log_file   = "terraform-plugin-proxmox.log"
  pm_debug      = true
  pm_log_levels = {
    _default    = "debug"
    _capturelog = ""
  }
}

variables.tf - Variable Declarations

variable "proxmox_api_url" {
  description = "Proxmox API URL"
  type        = string
  default     = "https://YOUR-PROXMOX-IP:8006/api2/json"
}

variable "proxmox_api_token_id" {
  description = "Proxmox API Token ID"
  type        = string
  sensitive   = true
}

variable "proxmox_api_token_secret" {
  description = "Proxmox API Token Secret"
  type        = string
  sensitive   = true
}

variable "proxmox_node" {
  description = "Proxmox node name"
  type        = string
  default     = "pve"
}

variable "vm_template_name" {
  description = "Name of the VM template to clone"
  type        = string
  default     = "ubuntu-22.04-template"
}

variable "ssh_public_key" {
  description = "SSH public key for cloud-init"
  type        = string
}

variable "default_gateway" {
  description = "Default gateway for VMs"
  type        = string
  default     = "192.168.1.1"
}

variable "dns_servers" {
  description = "DNS servers for VMs"
  type        = list(string)
  default     = ["1.1.1.1", "8.8.8.8"]
}

variable "vm_specs" {
  description = "VM specifications"
  type = map(object({
    cores   = number
    memory  = number
    disk    = string
    storage = string
    ip      = string
  }))
  default = {
    web = {
      cores   = 2
      memory  = 4096
      disk    = "32G"
      storage = "local-lvm"
      ip      = "192.168.1.100/24"
    }
    db = {
      cores   = 4
      memory  = 8192
      disk    = "100G"
      storage = "local-lvm"
      ip      = "192.168.1.101/24"
    }
  }
}

terraform.tfvars - Variable Values

# Add to .gitignore - contains secrets!
proxmox_api_url          = "https://192.168.1.10:8006/api2/json"
proxmox_api_token_id     = "terraform@pve!terraform-token"
proxmox_api_token_secret = "your-secret-token-here"
proxmox_node             = "pve"

ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC... your-key-here"

default_gateway = "192.168.1.1"
dns_servers     = ["192.168.1.1", "1.1.1.1"]

vm_specs = {
  web01 = {
    cores   = 2
    memory  = 4096
    disk    = "32G"
    storage = "local-lvm"
    ip      = "192.168.1.100/24"
  }
  web02 = {
    cores   = 2
    memory  = 4096
    disk    = "32G"
    storage = "local-lvm"
    ip      = "192.168.1.101/24"
  }
  db01 = {
    cores   = 4
    memory  = 8192
    disk    = "100G"
    storage = "local-lvm"
    ip      = "192.168.1.110/24"
  }
}

main.tf - Resource Definitions

# Create VMs from template
resource "proxmox_vm_qemu" "web_servers" {
  for_each = var.vm_specs

  name        = each.key
  target_node = var.proxmox_node
  desc        = "Managed by Terraform - ${each.key}"

  # Clone from template
  clone    = var.vm_template_name
  full_clone = true

  # VM settings
  cores   = each.value.cores
  sockets = 1
  memory  = each.value.memory

  # Boot order
  boot    = "order=scsi0"

  # Enable QEMU agent
  agent   = 1

  # OS type
  os_type = "cloud-init"

  # CPU type
  cpu     = "host"

  # BIOS
  bios    = "ovmf"

  # EFI disk
  efidisk {
    storage = each.value.storage
  }

  # Disk configuration
  disks {
    scsi {
      scsi0 {
        disk {
          size    = each.value.disk
          storage = each.value.storage
          iothread = true
        }
      }
    }
    ide {
      ide2 {
        cloudinit {
          storage = each.value.storage
        }
      }
    }
  }

  # Network configuration
  network {
    model  = "virtio"
    bridge = "vmbr0"
  }

  # Cloud-init configuration
  ipconfig0  = "ip=${each.value.ip},gw=${var.default_gateway}"
  nameserver = join(" ", var.dns_servers)
  ciuser     = "ubuntu"
  sshkeys    = var.ssh_public_key

  # Lifecycle
  lifecycle {
    ignore_changes = [
      network,
      disk,
    ]
  }

  # Wait for cloud-init to complete
  # Requires QEMU agent
  automatic_reboot = true
}

# LXC Container Example
resource "proxmox_lxc" "containers" {
  for_each = {
    docker-host = {
      cores   = 2
      memory  = 4096
      disk    = "20G"
      ip      = "192.168.1.120/24"
    }
  }

  hostname    = each.key
  target_node = var.proxmox_node
  ostemplate  = "local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst"

  # Container settings
  cores  = each.value.cores
  memory = each.value.memory
  swap   = 512

  # Features
  unprivileged = true
  features {
    nesting = true
    keyctl  = true
  }

  # Disk
  rootfs {
    storage = "local-lvm"
    size    = each.value.disk
  }

  # Network
  network {
    name   = "eth0"
    bridge = "vmbr0"
    ip     = each.value.ip
    gw     = var.default_gateway
  }

  # SSH key
  ssh_public_keys = var.ssh_public_key

  # Startup behavior
  onboot  = true
  start   = true
}

outputs.tf - Output Values

output "vm_ip_addresses" {
  description = "IP addresses of created VMs"
  value = {
    for k, v in proxmox_vm_qemu.web_servers :
    k => v.default_ipv4_address
  }
}

output "vm_ids" {
  description = "VM IDs in Proxmox"
  value = {
    for k, v in proxmox_vm_qemu.web_servers :
    k => v.vmid
  }
}

output "lxc_ip_addresses" {
  description = "IP addresses of LXC containers"
  value = {
    for k, v in proxmox_lxc.containers :
    k => v.network[0].ip
  }
}

Part 4: Working with Terraform

Initialize Terraform

# Initialize - downloads providers
terraform init

# Output:
# Initializing provider plugins...
# - Finding telmate/proxmox versions matching "~> 2.9.14"...
# - Installing telmate/proxmox v2.9.14...

Validate Configuration

# Check syntax
terraform validate

# Format code
terraform fmt -recursive

# Show planned changes
terraform plan

# Save plan to file
terraform plan -out=tfplan

Apply Configuration

# Apply changes
terraform apply

# Review plan and type 'yes' to confirm

# Or auto-approve (use with caution)
terraform apply -auto-approve

# Apply specific resource
terraform apply -target=proxmox_vm_qemu.web_servers["web01"]

View State

# List resources in state
terraform state list

# Show specific resource
terraform state show proxmox_vm_qemu.web_servers["web01"]

# Pull current state
terraform state pull > terraform.tfstate.backup

Destroy Resources

# Destroy everything
terraform destroy

# Destroy specific resource
terraform destroy -target=proxmox_vm_qemu.web_servers["web01"]

# Preview destruction
terraform plan -destroy

Part 5: Advanced Patterns

Module: Reusable VM Configuration

Create modules/proxmox-vm/main.tf:

variable "vm_name" {
  type = string
}

variable "target_node" {
  type = string
}

variable "template" {
  type = string
}

variable "cores" {
  type    = number
  default = 2
}

variable "memory" {
  type    = number
  default = 4096
}

variable "disk_size" {
  type    = string
  default = "32G"
}

variable "storage" {
  type    = string
  default = "local-lvm"
}

variable "ip_address" {
  type = string
}

variable "gateway" {
  type = string
}

variable "ssh_keys" {
  type = string
}

resource "proxmox_vm_qemu" "vm" {
  name        = var.vm_name
  target_node = var.target_node
  clone       = var.template

  cores   = var.cores
  memory  = var.memory
  agent   = 1
  os_type = "cloud-init"
  cpu     = "host"

  disks {
    scsi {
      scsi0 {
        disk {
          size    = var.disk_size
          storage = var.storage
        }
      }
    }
  }

  network {
    model  = "virtio"
    bridge = "vmbr0"
  }

  ipconfig0 = "ip=${var.ip_address},gw=${var.gateway}"
  sshkeys   = var.ssh_keys
}

output "vm_id" {
  value = proxmox_vm_qemu.vm.vmid
}

output "ip_address" {
  value = var.ip_address
}

Use module in main.tf:

module "web_servers" {
  source = "./modules/proxmox-vm"

  for_each = {
    web01 = { ip = "192.168.1.100/24", cores = 2, memory = 4096 }
    web02 = { ip = "192.168.1.101/24", cores = 2, memory = 4096 }
  }

  vm_name     = each.key
  target_node = var.proxmox_node
  template    = var.vm_template_name
  cores       = each.value.cores
  memory      = each.value.memory
  disk_size   = "32G"
  storage     = "local-lvm"
  ip_address  = each.value.ip
  gateway     = var.default_gateway
  ssh_keys    = var.ssh_public_key
}

Cloud-Init Configuration

Create templates/cloud-init/user-data.yaml:

#cloud-config
package_update: true
package_upgrade: true

packages:
  - qemu-guest-agent
  - curl
  - wget
  - git
  - vim
  - htop

# Run commands on first boot
runcmd:
  - systemctl enable qemu-guest-agent
  - systemctl start qemu-guest-agent
  - timedatectl set-timezone America/New_York

# Configure users
users:
  - name: admin
    groups: sudo
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - YOUR-SSH-PUBLIC-KEY

# Install Docker
write_files:
  - path: /tmp/install-docker.sh
    permissions: '0755'
    content: |
      #!/bin/bash
      curl -fsSL https://get.docker.com -o get-docker.sh
      sh get-docker.sh
      usermod -aG docker admin
      systemctl enable docker

runcmd:
  - /tmp/install-docker.sh

# Reboot after cloud-init completes
power_state:
  mode: reboot
  timeout: 300
  condition: True

Use with Terraform:

resource "proxmox_vm_qemu" "vm" {
  # ... other config ...

  cicustom = "user=local:snippets/user-data.yaml"
}

Dynamic VM Scaling

variable "web_server_count" {
  description = "Number of web servers"
  type        = number
  default     = 3
}

resource "proxmox_vm_qemu" "web_cluster" {
  count = var.web_server_count

  name        = "web-${count.index + 1}"
  target_node = var.proxmox_node
  clone       = var.vm_template_name

  cores  = 2
  memory = 4096

  # ... disk and network config ...

  ipconfig0 = "ip=192.168.1.${100 + count.index}/24,gw=${var.default_gateway}"
  sshkeys   = var.ssh_public_key
}

# Output all IPs
output "web_cluster_ips" {
  value = proxmox_vm_qemu.web_cluster[*].default_ipv4_address
}

Data Sources for Existing Resources

# Query existing VM template
data "proxmox_template" "ubuntu_template" {
  target_node = var.proxmox_node
  filter {
    name   = "name"
    values = ["ubuntu-22.04-template"]
  }
}

# Use in resource
resource "proxmox_vm_qemu" "vm" {
  clone = data.proxmox_template.ubuntu_template.name
  # ... rest of config ...
}

Conditional Resource Creation

variable "create_monitoring_vm" {
  type    = bool
  default = false
}

resource "proxmox_vm_qemu" "monitoring" {
  count = var.create_monitoring_vm ? 1 : 0

  name = "monitoring"
  # ... rest of config ...
}

Remote State Storage

Use S3-compatible storage for shared state:

terraform {
  backend "s3" {
    bucket                      = "terraform-state"
    key                         = "homelab/terraform.tfstate"
    region                      = "us-east-1"
    endpoint                    = "https://s3.homelab.local"
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    force_path_style            = true
  }
}

Or use Terraform Cloud:

terraform {
  cloud {
    organization = "my-homelab"

    workspaces {
      name = "homelab-infrastructure"
    }
  }
}

Part 6: Integration with Other Tools

Ansible Post-Provisioning

resource "proxmox_vm_qemu" "vm" {
  # ... VM configuration ...

  provisioner "local-exec" {
    command = <<-EOT
      sleep 60
      ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook \
        -i '${self.default_ipv4_address},' \
        -u ubuntu \
        --private-key ~/.ssh/id_rsa \
        playbook.yml
    EOT
  }
}

Packer Integration

# Build template with Packer first
resource "null_resource" "packer_build" {
  provisioner "local-exec" {
    command = "packer build ubuntu-template.pkr.hcl"
  }
}

# Then use template in Terraform
resource "proxmox_vm_qemu" "vm" {
  depends_on = [null_resource.packer_build]
  clone      = "ubuntu-packer-template"
  # ... rest of config ...
}

GitLab CI/CD Pipeline

Create .gitlab-ci.yml:

stages:
  - validate
  - plan
  - apply

variables:
  TF_ROOT: ${CI_PROJECT_DIR}
  TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/production

cache:
  paths:
    - ${TF_ROOT}/.terraform

before_script:
  - cd ${TF_ROOT}
  - terraform init

validate:
  stage: validate
  script:
    - terraform validate
    - terraform fmt -check

plan:
  stage: plan
  script:
    - terraform plan -out=tfplan
  artifacts:
    paths:
      - tfplan

apply:
  stage: apply
  script:
    - terraform apply -auto-approve tfplan
  when: manual
  only:
    - main
  dependencies:
    - plan

GitHub Actions

Create .github/workflows/terraform.yml:

name: Terraform

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  terraform:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: 1.6.6

    - name: Terraform Init
      run: terraform init
      env:
        TF_VAR_proxmox_api_token_id: ${{ secrets.PROXMOX_TOKEN_ID }}
        TF_VAR_proxmox_api_token_secret: ${{ secrets.PROXMOX_TOKEN_SECRET }}

    - name: Terraform Format
      run: terraform fmt -check

    - name: Terraform Validate
      run: terraform validate

    - name: Terraform Plan
      run: terraform plan
      env:
        TF_VAR_proxmox_api_token_id: ${{ secrets.PROXMOX_TOKEN_ID }}
        TF_VAR_proxmox_api_token_secret: ${{ secrets.PROXMOX_TOKEN_SECRET }}

    - name: Terraform Apply
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      run: terraform apply -auto-approve
      env:
        TF_VAR_proxmox_api_token_id: ${{ secrets.PROXMOX_TOKEN_ID }}
        TF_VAR_proxmox_api_token_secret: ${{ secrets.PROXMOX_TOKEN_SECRET }}

Part 7: Best Practices

State Management

# Always use remote state for teams
# Never commit terraform.tfstate to Git

# .gitignore
*.tfstate
*.tfstate.*
.terraform/
terraform.tfvars
*.tfplan

# Use state locking
terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "homelab/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Security Best Practices

# Never hardcode credentials
# Use environment variables or secret management

# BAD:
# provider "proxmox" {
#   pm_api_token_id     = "terraform@pve!token"
#   pm_api_token_secret = "secret-value"
# }

# GOOD:
provider "proxmox" {
  pm_api_token_id     = var.proxmox_api_token_id
  pm_api_token_secret = var.proxmox_api_token_secret
}

# Use sensitive variable flag
variable "proxmox_api_token_secret" {
  type      = string
  sensitive = true
}

# Encrypt state files
# Use HTTPS for remote state
# Implement least-privilege access

Code Organization

# Project structure for large deployments:
terraform-homelab/
├── environments/
│   ├── prod/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   ├── dev/
│   │   └── ...
│   └── staging/
│       └── ...
├── modules/
│   ├── compute/
│   │   ├── vm/
│   │   └── lxc/
│   ├── network/
│   └── storage/
├── policies/
│   └── sentinel/
└── scripts/
    └── helpers/

Resource Naming

# Use consistent naming conventions
locals {
  name_prefix = "${var.environment}-${var.project}"

  common_tags = {
    Environment = var.environment
    Project     = var.project
    ManagedBy   = "Terraform"
    Owner       = var.owner
  }
}

resource "proxmox_vm_qemu" "vm" {
  name = "${local.name_prefix}-web-${count.index + 1}"
  desc = jsonencode(local.common_tags)
  # ... rest of config ...
}

Testing Strategy

# Use terraform-compliance for policy testing
pip install terraform-compliance

# Create tests/security.feature
Feature: Security requirements
  Scenario: Ensure VMs use QEMU agent
    Given I have proxmox_vm_qemu defined
    Then it must contain agent
    And its value must be 1

# Run tests
terraform-compliance -f tests/ -p tfplan.json

# Validate with OPA (Open Policy Agent)
# Use Sentinel policies for Terraform Cloud

Part 8: Troubleshooting

Common Issues

Issue: Provider Plugin Not Found

# Solution: Re-initialize
rm -rf .terraform .terraform.lock.hcl
terraform init

Issue: State Lock Error

# Solution: Force unlock (use with caution)
terraform force-unlock LOCK_ID

# Better: Wait for other process to finish
# Or check DynamoDB/state backend for stale locks

Issue: VM Clone Fails

# Check template exists
# Verify Proxmox API credentials
# Check Proxmox logs
tail -f /var/log/pve/tasks/active

# Enable debug logging
export TF_LOG=DEBUG
terraform apply

Issue: Drift Detection

# Check for configuration drift
terraform plan -refresh-only

# Import manually created resources
terraform import proxmox_vm_qemu.existing pve/qemu/100

# Show current state
terraform show

Performance Optimization

# Use parallelism flag
terraform apply -parallelism=10

# Target specific resources
terraform apply -target=module.web_servers

# Refresh state less frequently
terraform plan -refresh=false

# Use smaller state files (separate workspaces)

Part 9: Real-World Examples

Complete Homelab Infrastructure

# Full homelab with multiple tiers
module "infrastructure" {
  source = "./modules/homelab"

  # Web tier
  web_servers = {
    count  = 3
    cores  = 2
    memory = 4096
    disk   = "50G"
  }

  # Application tier
  app_servers = {
    count  = 2
    cores  = 4
    memory = 8192
    disk   = "100G"
  }

  # Database tier
  db_servers = {
    count  = 2
    cores  = 8
    memory = 16384
    disk   = "200G"
  }

  # Monitoring
  monitoring = {
    cores  = 4
    memory = 8192
    disk   = "100G"
  }

  # Storage
  storage_nodes = {
    count = 3
    cores = 2
    memory = 4096
    disk = "500G"
  }
}

Disaster Recovery Setup

# Primary site
module "primary_site" {
  source = "./modules/site"

  site_name = "primary"
  proxmox_node = "pve-primary"
  # ... configuration ...
}

# DR site
module "dr_site" {
  source = "./modules/site"

  site_name = "dr"
  proxmox_node = "pve-dr"
  # Mirror primary configuration
}

Conclusion

Terraform transforms homelab infrastructure management from manual and error-prone to automated, repeatable, and version-controlled. By treating infrastructure as code, you gain consistency, documentation, and the ability to rebuild your entire environment with a single command.

Key Takeaways:

  • Terraform enables declarative infrastructure management
  • Use modules for reusability and organization
  • Always use remote state for team collaboration
  • Integrate with CI/CD for automated deployments
  • Combine with Packer for complete automation
  • Security: never commit secrets, use variables and secret management
  • Test configurations before applying to production

Next Steps:

  • Create VM templates with Packer (see our Packer guide)
  • Implement automated testing with terraform-compliance
  • Set up GitOps workflow with Git + Terraform + CI/CD
  • Explore Terragrunt for DRY configurations
  • Integrate with Vault for secret management
  • Implement policy as code with Sentinel or OPA

Happy automating!