☀️ Light Mode

Proxmox LXC Provisioning with OpenTofu

Proxmox LXC Provisioning with OpenTofu
Automated LXC Container Provisioning with OpenTofu and Proxmox

Automated LXC Container Provisioning with OpenTofu and Proxmox

Lightweight infrastructure automation with fully configured Docker-ready containers

While VMs provide robust isolation, LXC containers offer a lightweight alternative for workloads that don’t require separate kernels or operating systems. This guide extends our VM provisioning workflow to include automated container deployment, complete with Docker installation, user configuration, and SSH key management—all executed automatically through OpenTofu provisioners.

By the end of this tutorial, you’ll deploy production-ready Docker hosts in under 3 minutes with a single command.

Containers vs Cloud Images: Choosing the Right Tool

When to Use LXC Containers

  • Docker/container hosts
  • Web servers and APIs
  • Development environments
  • High-density deployments
  • Fast boot times required
  • Nested virtualization (with nesting feature)

When to Use Cloud Images

  • Standardized VM provisioning
  • Different kernel versions needed
  • Maximum security isolation
  • GPU passthrough required
  • Kernel module loading needed

Performance Comparison

Metric
Container
VM
Boot time
5-15 seconds
30-60 seconds
Memory overhead
~50MB
~512MB
Disk overhead
~500MB
~2GB
Creation time
2-3 minutes
5-8 minutes

Prerequisites

  • Completed VM provisioning setup (OpenTofu installed, API tokens configured)
  • Proxmox VE 8.0+ (LXC support required)
  • SSH key pair for authentication
  • Basic understanding of Linux containers

Part 1: Ubuntu LXC Template Setup

Unlike VM cloud images, LXC templates are pre-configured root filesystem archives. Proxmox provides official templates for various Linux distributions.

Step 1: Download Ubuntu Container Template

Proxmox provides official LXC templates for various Linux distributions. You can download them through the GUI or command line.

Option 1: Proxmox Web GUI (Recommended)

  1. Navigate to your shared storage (e.g., “shared-nfs”) → CT Templates
  2. Click the “Templates” button
  3. Select “ubuntu-24.04-standard_24.04-2_amd64.tar.zst”
  4. Click “Download” and wait for completion

Option 2: Command Line

Alternatively, SSH into your Proxmox node:

# Navigate to template storage directory
cd /var/lib/vz/template/cache

# Download Ubuntu 24.04 LTS container template
wget http://download.proxmox.com/images/system/ubuntu-24.04-standard_24.04-2_amd64.tar.zst

# Verify download
ls -lh ubuntu-24.04-standard_24.04-2_amd64.tar.zst

# Expected output:
# -rw-r--r-- 1 root root 128M Nov 15 10:30 ubuntu-24.04-standard_24.04-2_amd64.tar.zst

📁 Shared Storage for Templates

Store templates on shared storage (NFS, Ceph, etc.) so all Proxmox nodes can access them. In OpenTofu, reference the template using your shared storage name:

ostemplate = "shared-nfs:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst"
  • Replace shared-nfs with your actual shared storage name
  • vztmpl is Proxmox’s internal reference (filesystem path is template/cache/)

Step 2: Available Template Options

Proxmox offers templates for various distributions. View available templates:

# List available container templates
pveam available --section system

# Common templates:
# ubuntu-24.04-standard_24.04-2_amd64.tar.zst    (Ubuntu 24.04 LTS)
# ubuntu-22.04-standard_22.04-1_amd64.tar.zst    (Ubuntu 22.04 LTS)
# debian-12-standard_12.2-1_amd64.tar.zst        (Debian 12)
# alpine-3.19-default_20240207_amd64.tar.xz      (Alpine Linux)
# rockylinux-9-default_20221109_amd64.tar.xz     (Rocky Linux 9)

Part 2: OpenTofu Configuration for Containers

Understanding Container Configuration

Unlike VMs that use cloud-init for configuration, LXC containers require a different approach. We use OpenTofu provisioners to execute commands via SSH immediately after container creation. This allows us to install software, create users, and configure services automatically.

Container Configuration Workflow

OpenTofu creates container

Container boots (5-10 seconds)

SSH becomes available

Provisioner connects with root password

Execute configuration commands

Fully configured container ready

Create Container Configuration Files

In your existing ~/infrastructure/proxmox directory, create the following files:

lxc-main.tf – Container Resource Definition

resource "proxmox_lxc" "container" {
  target_node  = var.target_node
  hostname     = var.container_name
  ostemplate   = "shared-nfs:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst"
  vmid         = var.container_id
  cores        = var.container_cores
  memory       = var.container_memory

  # Root credentials for initial access
  password        = var.root_password
  ssh_public_keys = file(pathexpand(var.ssh_key_file))

  # Root filesystem configuration
  rootfs {
    storage = var.target_node == "pve1" ? "local-lvm" : "local"
    size    = "${var.container_disk_size}G"
  }

  # Network configuration with optional VLAN
  network {
    name   = "eth0"
    bridge = "vmbr0"
    ip     = var.use_dhcp ? "dhcp" : "${var.container_ip}/24"
    gw     = "10.0.0.1"
    tag    = var.vlan_tag
  }

  # DNS configuration
  nameserver   = "8.8.8.8 1.1.1.1"
  searchdomain = "local"

  # Container settings
  unprivileged = true
  start        = true

  # Post-creation configuration via SSH
  provisioner "remote-exec" {
    inline = [
      # Wait for container to fully boot
      "sleep 30",

      # Enable root SSH access with keys
      "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config",
      "sed -i 's/PermitRootLogin no/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config",
      "systemctl restart sshd",

      # Install prerequisites
      "apt update",
      "apt install -y ca-certificates curl gnupg",

      # Add Docker's official GPG key
      "install -m 0755 -d /etc/apt/keyrings",
      "curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc",
      "chmod a+r /etc/apt/keyrings/docker.asc",

      # Add Docker repository
      "echo 'Types: deb' > /etc/apt/sources.list.d/docker.sources",
      "echo 'URIs: https://download.docker.com/linux/ubuntu' >> /etc/apt/sources.list.d/docker.sources",
      "echo 'Suites: noble' >> /etc/apt/sources.list.d/docker.sources",
      "echo 'Components: stable' >> /etc/apt/sources.list.d/docker.sources",
      "echo 'Signed-By: /etc/apt/keyrings/docker.asc' >> /etc/apt/sources.list.d/docker.sources",

      # Install Docker and Docker Compose
      "apt update",
      "apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin",
      "systemctl enable docker",
      "systemctl start docker",

      # Create admin user with Docker access
      "useradd -m -s /bin/bash -G docker,sudo admin",
      "echo 'admin:${var.user_password}' | chpasswd",
      "mkdir -p /home/admin/.ssh",
      "echo '${var.admin_ssh_key}' > /home/admin/.ssh/authorized_keys",
      "chown -R admin:admin /home/admin/.ssh",
      "chmod 700 /home/admin/.ssh",
      "chmod 600 /home/admin/.ssh/authorized_keys",

      # Create ansible automation user
      "useradd -m -s /bin/bash -G docker,sudo ansible",
      "echo 'ansible:${var.user_password}' | chpasswd",
      "mkdir -p /home/ansible/.ssh",
      "echo '${var.ansible_ssh_key}' > /home/ansible/.ssh/authorized_keys",
      "chown -R ansible:ansible /home/ansible/.ssh",
      "chmod 700 /home/ansible/.ssh",
      "chmod 600 /home/ansible/.ssh/authorized_keys",

      # Configure passwordless sudo
      "echo 'admin ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/admin",
      "echo 'ansible ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/ansible",
      "chmod 0440 /etc/sudoers.d/admin",
      "chmod 0440 /etc/sudoers.d/ansible"
    ]

    # SSH connection configuration
    connection {
      type        = "ssh"
      user        = "root"
      host        = var.container_ip
      private_key = file(pathexpand(var.ssh_private_key_file))
      timeout     = "10m"
    }
  }
}

lxc-variables.tf – Variable Definitions

variable "container_name" {
  description = "Container hostname"
  type        = string
  default     = "test-lxc"
}

variable "container_id" {
  description = "Container ID (100-999999)"
  type        = number
  default     = 500
}

variable "container_cores" {
  description = "Number of CPU cores"
  type        = number
  default     = 2
}

variable "container_memory" {
  description = "RAM in MB"
  type        = number
  default     = 1024
}

variable "container_disk_size" {
  description = "Root filesystem size in GB"
  type        = number
  default     = 8
}

variable "container_ip" {
  description = "Static IP address for container"
  type        = string
  default     = "10.0.0.100"
}

variable "vlan_tag" {
  description = "VLAN tag (null for default/native VLAN)"
  type        = number
  default     = null
}

variable "use_dhcp" {
  description = "Use DHCP instead of static IP"
  type        = bool
  default     = false
}

variable "root_password" {
  description = "Root password for container creation"
  type        = string
  sensitive   = true
}

variable "ssh_key_file" {
  description = "Path to SSH public key file for root"
  type        = string
  default     = "~/.ssh/id_rsa.pub"
}

variable "ssh_private_key_file" {
  description = "Path to SSH private key for provisioning"
  type        = string
  default     = "~/.ssh/id_rsa"
}

variable "user_password" {
  description = "Password for admin and ansible users"
  type        = string
  sensitive   = true
  default     = ""
}

variable "admin_ssh_key" {
  description = "SSH public key for admin user"
  type        = string
  default     = ""
}

variable "ansible_ssh_key" {
  description = "SSH public key for ansible user"
  type        = string
  default     = ""
}

Update terraform.tfvars (Add Container Variables)

Add these lines to your existing terraform.tfvars file:

# Container-specific credentials
root_password   = "SecureR00tP@ssw0rd!"
user_password   = "SecureUs3rP@ssw0rd!"

# SSH public keys for container users
admin_ssh_key   = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBcD8kx... admin@workstation"
ansible_ssh_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpQ3rY... ansible@automation"

Part 3: Deploying Containers with OpenTofu

Initialize and Deploy

# If not already initialized, run:
tofu init

# Preview what will be created
tofu plan

# Deploy container
tofu apply

Real-World Deployment Scenarios

Example Deployments

# Scenario 1: Docker host for microservices
tofu apply \
  -var="container_name=docker-01" \
  -var="container_id=501" \
  -var="container_ip=10.0.0.151" \
  -var="target_node=pve1"

# Scenario 2: High-performance development environment
tofu apply \
  -var="container_name=dev-web" \
  -var="container_id=502" \
  -var="container_ip=10.0.0.152" \
  -var="container_cores=4" \
  -var="container_memory=4096" \
  -var="container_disk_size=50"

# Scenario 3: Database container with VLAN isolation
tofu apply \
  -var="container_name=postgres-01" \
  -var="container_id=503" \
  -var="container_ip=10.10.0.201" \
  -var="vlan_tag=10" \
  -var="container_memory=8192" \
  -var="container_disk_size=100"

# Scenario 4: DHCP-enabled container for dynamic environments
tofu apply \
  -var="container_name=test-env" \
  -var="container_id=504" \
  -var="use_dhcp=true" \
  -var="container_memory=2048"

# Scenario 5: Minimal web server (1 core, 512MB RAM)
tofu apply \
  -var="container_name=nginx-01" \
  -var="container_id=505" \
  -var="container_ip=10.0.0.155" \
  -var="container_cores=1" \
  -var="container_memory=512" \
  -var="container_disk_size=8"

Lightweight Infrastructure at Scale

You’ve extended your infrastructure-as-code workflow to include lightweight LXC containers. Combined with your VM provisioning capabilities, you can now deploy the right tool for each workload—VMs for isolation and different operating systems, containers for efficient, high-density deployments.

What You’ve Accomplished

  • Automated container deployment with Docker pre-installed
  • Implemented automated user and SSH key management
  • Created production-ready containers in under 3 minutes
  • Established consistent configuration across containers
  • Reduced resource overhead compared to VMs by 80-90%

Proxmox LXC Provisioning with OpenTofu
Proxmox LXC Provisioning with OpenTofu
NAXS Labs
Logo