Building Ubuntu 24.04 LTS Templates with Packer and HashiCorp Vault
Step-by-step guide to creating automated Ubuntu 24.04 LTS VM templates for Proxmox using Packer with HashiCorp Vault for secure credential management
This comprehensive guide shows you how to build production-ready Ubuntu 24.04 LTS (Noble Numbat) VM templates for Proxmox using HashiCorp Packer with Vault integration. Perfect for engineers implementing Infrastructure as Code in their homelab or enterprise environment.
The complete code for this guide is available in this repo https://github.com/tzalistar/packer-templates.
The packages I’m installing on these templates make sense in my case and I use them as an example. You can modify the templates and add packages that make more sense in your work load.
This guide is part of a series. Check out the companion guide for Building Debian Templates.
Prerequisites
Before starting, ensure you have:
Required Infrastructure
- Proxmox VE cluster (version 7.x or 8.x):
- One or more nodes with adequate resources
- Storage pool for VM disks (LVM, Ceph, NFS, etc.)
- Storage pool for ISO files
- Configured network bridge
- HashiCorp Vault instance:
- Running and unsealed
- KV v2 secrets engine enabled
- Valid authentication token
Required Software
- Packer (≥ 1.9.0) - Download
- Vault CLI - Configured with environment variables:
1 2
export VAULT_ADDR="https://vault.example.com:8200" export VAULT_TOKEN="your-vault-token"
Network Requirements
- Static IP address for Packer’s HTTP server (must be reachable from Proxmox VMs)
- Internet connectivity for ISO downloads and package installation
- No firewall blocking port 8100 (or your chosen HTTP port)
Overview
This automation creates an enterprise-ready Ubuntu 24.04 LTS template featuring:
- ✓ Cloud-init native configuration using autoinstall
- ✓ Dual user setup (admin + automation) with SSH keys
- ✓ Custom LVM partitioning with dedicated
/var/logpartition - ✓ QEMU guest agent for seamless Proxmox integration
- ✓ Serial console support for web-based console access
- ✓ UEFI boot with modern EFI configuration
- ✓ Subscription manager (ATIX) for Foreman/Katello
Architecture Overview
The build workflow:
- Packer retrieves credentials from HashiCorp Vault
- Downloads Ubuntu 24.04 LTS ISO to Proxmox
- Creates a temporary VM and boots the ISO
- Serves autoinstall config via HTTP
- Ubuntu’s autoinstall performs unattended installation
- Packer provisions additional software and configuration
- VM is cleaned and converted to a reusable template
Step 1: Configure HashiCorp Vault
1.1 Store Secrets in Vault
Ubuntu templates use the same Vault structure as Debian. Store your credentials securely:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Set Vault environment
export VAULT_ADDR="https://vault.example.com:8200"
export VAULT_TOKEN="hvs.XXXXXXXXXXXXXXXXXX"
# Create secrets (run once)
vault kv put kv/proxmox \
api_url="https://proxmox-server.example.com:8006/api2/json" \
api_token_id="packer@pve!packer-build" \
api_token_secret="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
default_user="admin" \
default_user_ssh_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC... admin@workstation" \
default_user_ssh_pass="AdminSecurePass123!" \
default_user_password_hash='$6$rounds=656000$SALT$HASH...' \
ansible_user="automation" \
ansible_user_ssh_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQD... automation@server" \
ansible_user_ssh_pass="AutomationPass456!" \
ansible_user_password_hash='$6$rounds=656000$SALT$HASH...'
Autoinstall vs Preseed: Ubuntu 24.04 uses cloud-init’s autoinstall instead of Debian’s preseed. This provides better cloud-native integration and more flexible configuration options.
1.2 Generate Password Hashes
Ubuntu’s cloud-init expects SHA-512 hashed passwords:
1
2
3
4
5
# Install mkpasswd (part of whois package on Debian/Ubuntu)
sudo apt-get install whois
# Generate hash
mkpasswd -m sha-512 'YourSecurePassword'
Output example:
1
$6$rounds=656000$SaltString$HashValue...
Use this hash for *_password_hash fields in Vault.
Step 2: Project Structure
2.1 Create Directory Layout
1
2
mkdir -p ubuntu-noble/packer/http
cd ubuntu-noble/packer
Your structure should look like:
1
2
3
4
5
6
7
8
9
ubuntu-noble/
├── packer/
│ ├── ubuntu-template.pkr.hcl # Main Packer config
│ ├── variables.pkr.hcl # Variable definitions
│ └── http/
│ ├── user-data.pkrtpl.hcl # Cloud-init autoinstall template
│ ├── meta-data # Cloud-init metadata (empty)
│ └── vendor-data # Cloud-init vendor data (empty)
└── README.md
2.2 Variable Configuration File
Create variables.pkr.hcl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# Variables for Ubuntu 24.04 LTS base template
variable "proxmox_node" {
type = string
description = "Proxmox node name (e.g., pve-node01)"
default = "pve-node01"
}
variable "vm_id" {
type = number
description = "VM template ID (null = auto-assign)"
default = null
}
variable "vm_name" {
type = string
description = "Template name"
default = "ubuntu-noble-base"
}
# ISO Configuration
variable "iso_url" {
type = string
description = "Ubuntu 24.04.3 LTS ISO URL"
default = "https://releases.ubuntu.com/noble/ubuntu-24.04.3-live-server-amd64.iso"
}
variable "iso_checksum" {
type = string
description = "ISO checksum"
default = "sha256:c3514bf0056180d09376462a7a1b4f213c1d6e8ea67fae5c25099c6fd3d8274b"
}
variable "iso_storage_pool" {
type = string
description = "Proxmox storage for ISO files"
default = "local"
}
variable "storage_pool" {
type = string
description = "Storage pool for VM disks"
default = "local-lvm"
}
# VM Hardware
variable "cpu_cores" {
type = number
description = "Number of CPU cores"
default = 2
}
variable "memory" {
type = number
description = "Memory in MB"
default = 2048
}
variable "disk_size" {
type = string
description = "Disk size (min 35G for custom partitioning)"
default = "35G"
}
# Network
variable "vlan_tag" {
type = number
description = "VLAN tag (0 = no VLAN)"
default = 0
}
variable "bridge" {
type = string
description = "Network bridge"
default = "vmbr0"
}
Step 3: Main Packer Configuration
The main template ubuntu-template.pkr.hcl follows a similar structure to Debian but uses Ubuntu’s autoinstall system.
3.1 Boot Command for Ubuntu Autoinstall
1
2
3
4
5
6
7
8
boot_wait = "5s"
boot_command = [
"<wait><esc><wait>",
"e<wait>",
"<down><down><down><end>",
" autoinstall ds=nocloud-net\;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/",
"<f10>"
]
Understanding the boot sequence:
<wait><esc><wait>: Interrupt GRUB boot menue<wait>: Enter edit mode for boot entry<down><down><down><end>: Navigate to kernel command lineautoinstall ds=nocloud-net...:autoinstall: Enable Ubuntu’s automated installerds=nocloud-net: Use NoCloud data source (HTTP)s=http://...: URL to fetch cloud-init config
<f10>: Boot with modified parameters
Key Difference: Ubuntu’s autoinstall is simpler than Debian’s preseed - it’s just adding two boot parameters instead of a long kernel command line.
Step 4: Cloud-Init Autoinstall Configuration
Create http/user-data.pkrtpl.hcl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#cloud-config
autoinstall:
version: 1
# Locale and keyboard
locale: en_US.UTF-8
keyboard:
layout: us
# Network - use DHCP during install
network:
network:
version: 2
ethernets:
any:
match:
name: "en*"
dhcp4: true
# Storage configuration with custom LVM
storage:
layout:
name: lvm
config:
- type: disk
id: disk0
ptable: gpt
wipe: superblock
preserve: false
grub_device: true
# EFI partition
- type: partition
id: partition-efi
device: disk0
size: 512M
flag: boot
wipe: superblock
preserve: false
- type: format
id: format-efi
volume: partition-efi
fstype: fat32
preserve: false
- type: mount
id: mount-efi
device: format-efi
path: /boot/efi
# Boot partition
- type: partition
id: partition-boot
device: disk0
size: 1G
wipe: superblock
preserve: false
- type: format
id: format-boot
volume: partition-boot
fstype: ext4
preserve: false
- type: mount
id: mount-boot
device: format-boot
path: /boot
# LVM partitions (root, /var/log, /tmp, /var)
# ... (similar structure for other partitions)
# Identity - temporary during install
identity:
hostname: ubuntu-template
username: ${default_user}
password: ${default_user_password}
# SSH configuration
ssh:
install-server: true
allow-pw: true
# Package selection
packages:
- qemu-guest-agent
- cloud-init
- net-tools
- vim
- curl
- wget
# Late commands (run after install)
late-commands:
# Create automation user
- curtin in-target --target=/target -- useradd -m -s /bin/bash ${ansible_user}
- curtin in-target --target=/target -- usermod -aG sudo ${ansible_user}
# Setup SSH and sudo access
# ... (user configuration continues)
Declarative Storage: Unlike Debian’s recipe-based partitioning, Ubuntu uses a declarative YAML config. This makes it easier to read and modify partition layouts.
4.1 Create Meta-Data File
Create empty http/meta-data:
1
2
# Empty meta-data file
# Required by cloud-init NoCloud datasource
Step 5: Build and Use
The build process is identical to Debian:
1
2
3
4
5
6
7
8
9
10
11
# Initialize
packer init .
# Validate
packer validate .
# Build
packer build \
-var="proxmox_node=pve-node01" \
-var="bridge=vmbr0" \
.
Comparison: Ubuntu vs Debian Templates
| Feature | Ubuntu 24.04 | Debian 13 |
|---|---|---|
| Installer | Autoinstall (cloud-init) | Preseed |
| Config Format | YAML | Preseed text |
| Boot Command | Simpler (GRUB edit) | More complex (GRUB cmdline) |
| LVM Config | Declarative YAML | Recipe format |
| Cloud-init | Native support | Requires installation |
| Package Ecosystem | PPAs available | Debian repos only |
| Release Cycle | 6 months / 2 years LTS | Stable + Testing tracks |
When to Choose Ubuntu: Use Ubuntu for better hardware support, PPAs, and commercial support. Choose Debian for stability-first approach and predictable release cycles.
Troubleshooting
Issue: HTTP server unreachable
Symptoms: Installation hangs at “Configuring autoinstall”
Solution:
1
2
3
4
5
6
7
8
9
10
# Verify HTTP server accessibility
curl http://YOUR_IP:8100/user-data
# Test from Proxmox node
ssh root@proxmox-node
curl http://YOUR_IP:8100/user-data
# Check firewall
sudo ufw status
sudo ufw allow 8100/tcp # If using UFW
Issue: Autoinstall fails with partitioning error
Symptoms: “Storage configuration failed”
Solution:
- Ensure disk size is at least 35GB
- Check Proxmox storage has available space
- Verify storage pool supports your disk format
Conclusion
You’ve successfully built an automated, reproducible Ubuntu 24.04 LTS template using modern IaC practices. This setup provides:
✓ Security: Credentials managed by Vault ✓ Repeatability: Build identical templates on-demand ✓ Flexibility: Customize via variables ✓ Cloud-native: Full cloud-init integration ✓ Production-ready: Custom partitioning and hardening
Complete File Reference
All files for this guide are available at: https://github.com/tzalistar/packer-templates
Questions or improvements? Reach out or submit a pull request!
