Blog

Proxmox LXC and Terraform

By Jay

Jul 14, 2023 | 5 minutes read

Series: HomeLab

Tags: blog, tech, terraform, proxmox

In an earlier post, I provided details on how I use terraform to manage virtual machines in my Proxmox installation. This time, we’re going to deploy Linux Containers to Proxmox via Terraform.

Why LXC? For workloads that do not require a full virtual machine, LXC provides a lightweight virtualization technology that uses the host’s kernel This makes LXC containers more resource efficient, faster to start, and more performant than a virtual machine. Each container operates in its own namespace, offering application isolation with its own network stack, process space, and mount points.

Why not LXC? Anything that gets too deep into playing with kernel bits - such as nested virtualization, using certain devices, etc - will likely have a bad time. That’s not to say you shouldn’t try them - I mean where is the fun in that? - but you should be ready to endure failure.

Proxmox provides a variety of basic templates for the most common Linux distributions, as well as Turnkey Linux container images. These templates can be viewed and downloaded from the Proxmox command line using the pveam utility. Since disk is cheap, I just have an ansible playbook that I run periodically that finds all the available templates and downloads them:

---
- hosts: all
  gather_facts: no
  tasks:
    - name: Update the list of container images
      command: pveam update
      become: yes

    - name: Get list of available templates
      shell: "pveam available"
      register: available_templates

    - name: Download templates
      shell: "pveam download iris {{ item.split()[1] }}"
      loop: "{{ available_templates.stdout_lines }}"
      ignore_errors: yes
      become: yes

    - name: List all installed container templates
      shell: "pveam list local"
      register: installed_templates

    - name: Output all installed container templates
      debug:
        var: installed_templates.stdout_lines

We’re going to use the same terraform provide that we used for our VM deployment - Telmate - because it also provides us with the ability to manage containers.

It’s important to note that there are some differences between containers and vms. For one, the containers all are logged into as the root user. The console can be accessed on the host system, or via the proxmox gui. You can provide a password during the installation process, or if you are accessing over the network you can use an ssh key that is provided at provision time.

So, let’s get to deploying some containers!

First, we set our variables:

# Proxmox API configuration
proxmox_api_url      = "https://<proxmox-api-url>"
proxmox_username     = "<username>@<auth-realm>"
proxmox_password     = "<password>"
proxmox_tls_insecure = "<true-or-false>"

# Container configuration
container_count = <number-of-containers>
name_prefix = "<container-name-prefix>"
template = "<template-location>"
storage = "<storage-location>"
user_password = "<user-password>"
ssh_keys = "<sshkeys-in-heredoc-form"

# Resource allocation
cores = <number-of-cores>
disk_size = "<disk-size>"
memory = <memory-size>

# Network configuration
network_model = "<network-model>"
network_bridge = "<network-bridge>"
network_ipv4 = "<network-ipv4>"

# Node configuration
node = [
"<node1>", "<node2>", "<node3>"
]

Then, we have our main terraform file:

terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = ">= 2.0.0"
    }
  }
}

variable "proxmox_api_url" {
  description = "The API URL for the Proxmox provider."
  type = string
}

variable "proxmox_username" {
  description = "The user for the Proxmox provider."
  type = string
}

variable "proxmox_password" {
  description = "The password for the Proxmox provider."
  type = string
  sensitive = true
}

variable "user_password" {
  description = "The password for the LXC user."
  type = string
  sensitive = true
}

variable "proxmox_tls_insecure" {
  description = "Whether to disable TLS verification for the Proxmox provider."
  type = bool
}

variable "storage" {
  description = "The storage to use for the containers."
  type = string
}

variable "name_prefix" {
  description = "The prefix for the name of the containers."
  type = string
}

variable "node" {
  description = "The node where you want the containers to be created at."
  type = list(string)
}

variable "container_count" {
  description = "The number of containers to create."
  type = number
}

variable "template" {
  description = "The template to use for creating the containers."
  type = string
}

variable "memory" {
  description = "The amount of memory for the containers."
  type = number
}

variable "cores" {
  description = "The number of cores for the containers."
  type = number
}

variable "disk_size" {
  description = "The disk size for the containers."
  type = string
}

variable "network_model" {
  description = "The network model for the containers."
  type = string
}

variable "network_bridge" {
  description = "The network bridge for the containers."
  type = string
}

variable "network_ipv4" {
  description = "The IPv4 address for the containers."
  type = string
}

variable "ssh_keys" {
  description = "The SSH keys to use for the VMs."
  type = string
}

provider "proxmox" {
  pm_api_url      = var.proxmox_api_url
  pm_user         = var.proxmox_username
  pm_password     = var.proxmox_password
  pm_tls_insecure = var.proxmox_tls_insecure
}

resource "proxmox_lxc" "example_container" {
  count        = var.container_count
  hostname     = "${var.name_prefix}${count.index}"
  ostemplate   = var.template
  unprivileged = true
  target_node  = var.node[count.index % length(var.node)]
  memory       = var.memory
  cores        = var.cores
  password     = var.user_password
  onboot       = true
  hastate      = "started"
  ssh_public_keys = var.ssh_keys

  rootfs {
    storage = var.storage
    size    = var.disk_size
  }

  network {
    name  = "eth0"
    bridge   = var.network_bridge
    ip     = var.network_ipv4
    ip6     = "auto"
  }
}


output "container_name" {
  description = "The names of the created containers"
  value       = [for i in proxmox_lxc.example_container : i.hostname]
}

Just like with our other example, to run, you simply update the variables file with your parameters and then do the normal “terraform plan / terraform apply” dance, being sure to pass the variables file along:

$ terraform plan -var-file="variables.tfvars"
$ terraform apply -var-file="variables.tfvars"

When you are done, you can simply remove your deployed infrastructure with:

$ terraform destroy -var-file="variables.tfvars"
  1. Just a reminder; since I know that cutting/pasting out of a blog post is suboptimal, I also have these and other scripts that I use for building out my development infrastructure in GitHub. These scripts embedded here are pulled from GH when the site gets
    built, but be aware that these scripts are under pretty heavy development (which is a nice way of saying I’ll be making mistakes, breaking things, fixing things, etc in that repo).