Note: This is the same Terraform Phoenix Cycle, but replace miniblue.io with Proxmox.

This article focuses on transitioning our infrastructure design from local emulation layers to dedicated bare-metal server hardware using Proxmox VE.

Unlike localized development tools, Proxmox allows your automated infrastructure to scale seamlessly across physical hardware via Proxmox clustering. By utilizing the native Proxmox Terraform provider (bpg/proxmox), we can replace hacky shell scripts with structured API calls to dynamically spin up, orchestrate, and collapse lightweight Linux Containers (LXC) or full Virtual Machines (VMs).

We call this the Provision-Orchestrate-Collapse-Destroy cycle.

1. The Provisioning Phase

We interact directly with the Proxmox hypervisor layer. Instead of requiring administrative root passwords, Terraform connects securely using an unprivileged API token configured with targeted role permissions.

We define a native proxmox_virtual_environment_container (LXC) to act as our compute canvas. Proxmox maps a clean, minimal operating system image template directly from local storage pools (local-lvm or local-zfs) into the new container instance.

2. The Orchestration Phase

Once the hypervisor allocates the hardware resources, the instance must join the broader workspace network. By map the container’s network block to a physical or virtual network bridge (vmbr0). This instantly grants the container a real, routable IP address on the local network interface.

Then execute our targeted deployment payload inside the isolated guest environment—such as configuring an automated Nginx reverse proxy gateway.

3. The Condition

Because our environment runs across real network interfaces rather than a local loopback device, the orchestration layer evaluates system health using automated network verification logic. The pipeline actively listens for a successful state validation or task-completion signal over the network interface.

4. The Teardown

Once validation finishes, we return the physical host back to its baseline. Terraform signals the Proxmox API to terminate the active container processes. Proxmox safely detaches the storage volumes and scrubs the local disk blocks.

The cluster map updates, returning 100% of the allocated CPU cores and RAM segments back to the hypervisor pool, leaving the physical hardware entirely pristine.

Example Implementation

Create Proxmox Container (LXC)
↓
Inject Base Operating System
↓
Deploy Nginx Reverse Proxy Services
↓
Validate Gateway Network Health
↓
Collapse Guest Workloads
↓
Destroy Proxmox Container Allocation
↓
Reclaim Bare-Metal System Resources

Directory Layout

Phoenix-Proxmox-ReverseProxy/
├── main.tf
├── providers.tf
├── variables.tf
├── versions.tf
│
├── config/
│   └── nginx-default.conf
│
└── scripts/
├── wait_proxy_ready.sh
├── validate_destroy.sh
├── collapse_workloads.sh
└── destroy_everything.sh

Version Configuration

filename: versions.tf


terraform {
  required_version = ">= 1.5.0"

  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "~> 0.106.0"
    }
    null = {
      source  = "hashicorp/null"
      version = "~> 3.2"
    }
  }
}

Provider Configuration

filename: ‘providers.tf’

provider "proxmox" {
  endpoint = var.pm_api_url
  api_token = var.pm_api_token
  insecure  = true # Set to false if utilizing verified SSL certificates
}

provider "null" {}

Variables

filename: variables.tf

variable "pm_api_url" {
  type        = string
  description = "The Proxmox VE API endpoint URL (e.g., https://192.168.1.50:8006/api2/json)"
}

variable "pm_api_token" {
  type        = string
  secret      = true
  description = "The unprivileged API Token secret string (UserID!TokenID=SecretUUID)"
}

variable "target_node" {
  type    = string
  default = "pve-host"
}

variable "container_ip" {
  type    = string
  default = "192.168.1.50"
}

Main

filename: main.tf

##########################################################
# STEP 1: Provision
# Provision the bare-metal LXC Container via Proxmox API
##########################################################

resource "proxmox_virtual_environment_container" "proxy_gateway" {
  node_name = var.target_node
  vm_id     = 9000
  hostname  = "nginx-phoenix-gateway"

  initialization {
    architecture = "amd64"
    os_template  = "local:vztmpl/debian-12-standard.tar.zst"

    ip_config {
      ipv4 {
        address = "${var.container_ip}/24"
        gateway = "192.168.1.1"
      }
    }
  }

  cpu {
    cores = 2
  }

  memory {
    dedicated = 1024
  }
}

##########################################################
# STEP 2: Orchestrate
# Inject Nginx configurations and deploy the network service
##########################################################

resource "null_resource" "deploy_proxy_config" {
  depends_on = [proxmox_virtual_environment_container.proxy_gateway]

  connection {
    type     = "ssh"
    user     = "root"
    host     = var.container_ip
    private_key = file("~/.ssh/id_rsa")
  }

  provisioner "file" {
    source      = "config/nginx-default.conf"
    destination = "/etc/nginx/conf.d/default.conf"
  }

  provisioner "remote-exec" {
    inline = [
      "apt-get update && apt-get install -y nginx",
      "systemctl restart nginx"
    ]
  }
}

##########################################################
# STEP 3: Verification Loop
# Wait for remote HTTP port readiness
##########################################################

resource "null_resource" "wait_proxy_ready" {
  depends_on = [null_resource.deploy_proxy_config]

  provisioner "local-exec" {
    command = "bash scripts/wait_proxy_ready.sh ${var.container_ip}"
  }
}

##########################################################
# STEP 4: Pre-Collapse Validation
# Validate state consistency before triggering destruction
##########################################################

resource "null_resource" "validate_destroy" {
  depends_on = [null_resource.wait_proxy_ready]

  provisioner "local-exec" {
    command = "bash scripts/validate_destroy.sh ${var.container_ip}"
  }
}

##########################################################
# STEP 5: Collapse Guest Workloads
# Stop active tasks inside the container prior to hardware deletion
##########################################################

resource "null_resource" "collapse_workloads" {
  depends_on = [null_resource.validate_destroy]

  provisioner "local-exec" {
    command = "bash scripts/collapse_workloads.sh ${var.container_ip}"
  }
}

Nginx Configuration payload

filename: config/nginx-default.conf

server {
    listen 80;
    server_name localhost;

    location / {
        root /var/www/html;
        index index.html;
        add_header X-Phoenix-Cycle "Active" always;
    }
}

Automation Scripts

filename: scripts/wait_proxy_ready.sh

#!/usr/bin/env bash

set -e
TARGET_IP=$1

echo "[STEP] Verifying network path to Proxmox guest instance..."

MAX_RETRY=20
COUNT=0

while true; do
    if curl -s -I "http://${TARGET_IP}:80" | grep -q "200 OK"; then
        echo "[OK] Nginx service reachable on target IP."
        exit 0
    fi

    COUNT=$((COUNT+1))
    if [ "$COUNT" -ge "$MAX_RETRY" ]; then
        echo "[ERROR] Target proxy failed to stabilize within window."
        exit 1
    fi

    sleep 3
done

filename: scripts/validate_destroy.sh

#!/usr/bin/env bash

set -e
TARGET_IP=$1

echo "[STEP] Running infrastructure verification pre-collapse..."

# CHECK 1: Ensure custom header signature is active
HTTP_HEADER=$(curl -s -I "http://${TARGET_IP}:80" | grep -i "X-Phoenix-Cycle")

if [ -z "$HTTP_HEADER" ]; then
    echo "[ERROR] Validation failed: Integrity token mismatch."
    exit 1
fi

echo "[OK] Integrity validation successful."

filename: scripts/collapse_workloads.sh

#!/usr/bin/env bash

set -e
TARGET_IP=$1

echo "[STEP] Gracefully collapsing active guest connections..."

# Terminate internal daemons gracefully before hypervisor destruction
ssh -o StrictHostKeyChecking=no root@${TARGET_IP} "systemctl stop nginx"

echo "[DONE] Workloads collapsed."

filename: scripts/destroy_everything.sh

#!/usr/bin/env bash

set -e

echo "[STEP] Requesting hypervisor asset reclamation via Terraform..."

terraform destroy -auto-approve

echo "[DONE] Bare-metal environment returned to baseline."

Execution Instructions

Initialize the Proxmox provider schema:

terraform init

Execute the full bare-metal provisioning and verification pipeline:

terraform apply -auto-approve

Collapse workloads and reclaim resources back to the Proxmox pool:

bash scripts/destroy_everything.sh

Special Note

In our configuration, the destruction sequence runs exactly in reverse order of the defined dependency chain. Because the collapse_workloads resource relies explicitly on validate_destroy, Terraform guarantees the software processes running inside the guest OS are safely stopped before it initiates the destruction of the underlying hypervisor resource container.

While null_resource execution scripts are paired here alongside native resources to handle remote orchestration tasks smoothly, this establishes a scalable bare-metal automation template that entirely removes dependency on local workstation loops.