Add Packer template for building Ubuntu 24.04 server image

Build a QCOW2 image locally with QEMU/KVM + autoinstall, then provision
with the existing Ansible playbook. Allows testing changes locally before
deploying to production. Outputs a ready-to-upload image for hosting providers.

Usage: cp server.pkrvars.hcl.example server.pkrvars.hcl, fill in values,
then run ./build.sh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nelis Volschenk 2026-05-04 07:28:30 +00:00
parent 2236f60469
commit 06580f9db6
6 changed files with 249 additions and 0 deletions

3
packer/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
output/
server.pkrvars.hcl
packer_cache/

35
packer/build.sh Executable file
View file

@ -0,0 +1,35 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
if [ ! -f server.pkrvars.hcl ]; then
echo "Error: server.pkrvars.hcl not found."
echo "Copy server.pkrvars.hcl.example to server.pkrvars.hcl and fill in your values."
exit 1
fi
echo "=== Initializing Packer plugins ==="
packer init ubuntu-server.pkr.hcl
echo ""
echo "=== Building image ==="
echo "This will take 15-30 minutes depending on your machine."
echo ""
packer build -var-file=server.pkrvars.hcl ubuntu-server.pkr.hcl
echo ""
echo "==========================================="
echo " Image built successfully!"
echo "==========================================="
echo " Output: $SCRIPT_DIR/output/packer-ubuntu-server"
echo ""
echo " To test locally:"
echo " qemu-system-x86_64 -m 4096 -hda output/packer-ubuntu-server -enable-kvm"
echo ""
echo " To convert for other formats:"
echo " qemu-img convert -f qcow2 -O raw output/packer-ubuntu-server output/server.raw"
echo " qemu-img convert -f qcow2 -O vmdk output/packer-ubuntu-server output/server.vmdk"
echo "==========================================="

0
packer/http/meta-data Normal file
View file

31
packer/http/user-data Normal file
View file

@ -0,0 +1,31 @@
#cloud-config
autoinstall:
version: 1
locale: en_US.UTF-8
keyboard:
layout: us
network:
version: 2
ethernets:
id0:
match:
driver: virtio_net
dhcp4: true
storage:
layout:
name: lvm
sizing-policy: all
identity:
hostname: server
username: ubuntu
password: "$6$XjqGOA0giVyHdSZ0$9YqNQlsfuU5xo8uLqpnD49mS3KqyMSp.6imNnIQE18obgTyE0g8LOcL6hvg0sJQXgHv6S7HSIBpKY0.keMXKU."
ssh:
install-server: true
allow-pw: true
packages:
- openssh-server
- curl
- wget
late-commands:
- "echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/ubuntu"
- "chmod 440 /target/etc/sudoers.d/ubuntu"

View file

@ -0,0 +1,7 @@
base_domain = "example.com"
ssh_pubkey = "ssh-ed25519 AAAA... user@host"
juicefs_s3_endpoint = "https://s3.amazonaws.com"
juicefs_s3_bucket = "my-nextcloud-bucket"
juicefs_s3_access_key = "AKIAIOSFODNN7EXAMPLE"
juicefs_s3_secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
juicefs_cache_size = "50G"

View file

@ -0,0 +1,173 @@
packer {
required_plugins {
qemu = {
version = "~> 1"
source = "github.com/hashicorp/qemu"
}
ansible = {
version = ">= 1.1.2"
source = "github.com/hashicorp/ansible"
}
}
}
# --- VM settings ---
variable "cpu" {
type = string
default = "2"
}
variable "ram" {
type = string
default = "4096"
}
variable "disk_size" {
type = string
default = "50000"
}
variable "headless" {
type = bool
default = true
}
# --- Ubuntu ISO ---
variable "iso_url" {
type = string
default = "https://releases.ubuntu.com/24.04/ubuntu-24.04.2-live-server-amd64.iso"
}
variable "iso_checksum" {
type = string
default = "file:https://releases.ubuntu.com/24.04/SHA256SUMS"
}
# --- OS user (created by autoinstall) ---
variable "ssh_username" {
type = string
default = "ubuntu"
}
variable "ssh_password" {
type = string
default = "ubuntu"
sensitive = true
}
# --- Server config (passed to Ansible) ---
variable "base_domain" {
type = string
}
variable "ssh_pubkey" {
type = string
default = ""
}
variable "juicefs_s3_endpoint" {
type = string
}
variable "juicefs_s3_bucket" {
type = string
}
variable "juicefs_s3_access_key" {
type = string
sensitive = true
}
variable "juicefs_s3_secret_key" {
type = string
sensitive = true
}
variable "juicefs_cache_size" {
type = string
default = "50G"
}
source "qemu" "ubuntu-server" {
accelerator = "kvm"
boot_command = [
"c<wait>",
"linux /casper/vmlinuz --- autoinstall ds=\"nocloud;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/\"<enter><wait>",
"initrd /casper/initrd<enter><wait>",
"boot<enter><wait>"
]
boot_wait = "10s"
disk_cache = "none"
disk_compression = true
disk_discard = "unmap"
disk_interface = "virtio"
disk_size = var.disk_size
format = "qcow2"
headless = var.headless
http_directory = "http"
iso_checksum = var.iso_checksum
iso_url = var.iso_url
net_device = "virtio-net"
output_directory = "output"
qemu_binary = "/usr/bin/qemu-system-x86_64"
qemuargs = [
["-m", "${var.ram}M"],
["-smp", var.cpu],
["-cpu", "host"]
]
shutdown_command = "echo '${var.ssh_password}' | sudo -S shutdown -P now"
ssh_password = var.ssh_password
ssh_username = var.ssh_username
ssh_handshake_attempts = 500
ssh_timeout = "45m"
ssh_wait_timeout = "45m"
}
build {
sources = ["source.qemu.ubuntu-server"]
provisioner "shell" {
execute_command = "echo '${var.ssh_password}' | sudo -S bash -c '{{ .Vars }} {{ .Path }}'"
inline = [
"apt-get update",
"apt-get install -y ansible-core python3-pip"
]
}
provisioner "file" {
source = "../playbook.yml"
destination = "/tmp/playbook.yml"
}
provisioner "file" {
source = "../resources"
destination = "/tmp/resources"
}
provisioner "shell" {
execute_command = "echo '${var.ssh_password}' | sudo -S bash -c '{{ .Vars }} {{ .Path }}'"
environment_vars = [
"ANSIBLE_FORCE_COLOR=1"
]
inline = [
"ansible-playbook -i localhost, -c local /tmp/playbook.yml -e 'base_domain=${var.base_domain} ssh_pubkey=\"${var.ssh_pubkey}\" juicefs_s3_endpoint=${var.juicefs_s3_endpoint} juicefs_s3_bucket=${var.juicefs_s3_bucket} juicefs_s3_access_key=${var.juicefs_s3_access_key} juicefs_s3_secret_key=${var.juicefs_s3_secret_key} juicefs_cache_size=${var.juicefs_cache_size}'"
]
}
provisioner "shell" {
execute_command = "echo '${var.ssh_password}' | sudo -S bash -c '{{ .Vars }} {{ .Path }}'"
inline = [
"rm -rf /tmp/playbook.yml /tmp/resources",
"apt-get clean",
"rm -rf /var/lib/apt/lists/*",
"cloud-init clean --logs",
"truncate -s 0 /etc/machine-id",
"rm -f /var/lib/dbus/machine-id",
"fstrim -av || true"
]
}
}