Cloudbuilder is a tool for managing VM templates in Proxmox. It automates downloading cloud OS images, customizing them, and importing them as Proxmox VM templates.
- Template Management: Download, customize, and import VM templates
- File Import: Copy files from host into template images during build
- Standalone Mode: Build templates locally without Proxmox (auto-detected or via
--build-only) - Import Pre-built Images: Import existing qcow2/img files from local paths or URLs
- Minimal Downtime: Updates templates with minimal unavailability in Proxmox
- Template Filtering: Process only specific templates with
--onlyand--except - Status Reporting: Show template status without making changes
- Metadata Tracking: Track build dates, update dates, and VMIDs
- Automatic Storage Detection: Automatically selects compatible Proxmox storage
- Shell Completions: Tab completion for bash and zsh
- Self-Update: Update cloudbuilder directly from git
- Python 3.6+
- libguestfs-tools (for virt-customize)
For Proxmox integration (optional):
- Proxmox VE 6.0+
- Proxmox CLI tools (qm, pvesh)
Without Proxmox, cloudbuilder runs in standalone mode and builds templates locally.
# Clone the repository
cd /opt && git clone https://github.com/iandk/cloudbuilder.git
cd cloudbuilder
# Install dependencies
pip install -r requirements.txt
# Make script executable
chmod +x cloudbuilder.py
# Create symlink
ln -s /opt/cloudbuilder/cloudbuilder.py /usr/local/bin/cloudbuilderCreate a templates.json file in the current directory:
{
"debian-12": {
"image_url": "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2",
"install_packages": ["qemu-guest-agent", "curl", "git"],
"update_packages": false,
"run_commands": [
"apt-get update && apt-get -y dist-upgrade",
"systemctl enable qemu-guest-agent",
"rm -f /etc/ssh/ssh_host_*",
"systemctl enable ssh",
"rm -rf /var/lib/cloud/instance /var/lib/cloud/data",
"truncate -s 0 /etc/machine-id"
],
"ssh_password_auth": false,
"ssh_root_login": false
}
}Use copy_files to embed files into the template image. Files are copied after package installation but before run_commands, so they're available for commands to use.
{
"my-template": {
"image_url": "https://example.com/image.qcow2",
"install_packages": ["nginx"],
"copy_files": {
"files/nginx.conf": "/etc/nginx/",
"files/scripts/setup.sh": "/usr/local/bin/"
},
"run_commands": [
"chmod +x /usr/local/bin/setup.sh",
"/usr/local/bin/setup.sh"
]
}
}- Keys: Local file paths (relative to cloudbuilder directory or absolute)
- Values: Destination directories inside the image (must end with
/)- Correct:
"/etc/nginx/"- copies file into /etc/nginx/ - Wrong:
"/etc/nginx/nginx.conf"- this will fail (not a directory)
- Correct:
- Recommended: Store files in the
files/directory in the cloudbuilder root
| Option | Description |
|---|---|
image_url |
URL to download the cloud image |
install_packages |
List of packages to install |
update_packages |
Whether to update packages via virt-customize (recommend false, do manual upgrade in run_commands instead) |
copy_files |
Object mapping local file paths to destination directories in the image |
run_commands |
Custom commands to run during customization |
ssh_password_auth |
Enable password authentication for SSH |
ssh_root_login |
Allow root login via SSH |
Note: For reliable builds, set update_packages: false and add the upgrade command as the first entry in run_commands:
- Debian/Ubuntu:
apt-get update && apt-get -y dist-upgrade - RHEL/Fedora:
sudo dnf -y upgrade
# Check and build missing templates
./cloudbuilder.py
# Show status without making changes
./cloudbuilder.py --status
# Update existing templates
./cloudbuilder.py --update
# Rebuild templates from scratch
./cloudbuilder.py --rebuild
# Update cloudbuilder itself
./cloudbuilder.py --self-update
# Force update (discards local changes if any)
./cloudbuilder.py --self-update --force
# Set up shell tab completions
./cloudbuilder.py --setup-completionsCloudbuilder can run on systems without Proxmox VE installed. It automatically detects the environment and adjusts behavior accordingly.
# On a non-Proxmox system, standalone mode is automatic
./cloudbuilder.py --status
# WARNING Proxmox VE not detected - running in standalone mode
# Build templates locally (downloads and customizes images)
./cloudbuilder.py --rebuild --only debian-12
# Images are saved to /var/lib/cloudbuilder/templates/
# On a Proxmox system, explicitly skip import
./cloudbuilder.py --build-only --only debian-12
# INFO Standalone mode enabled - skipping Proxmox importIn standalone mode:
- Templates are downloaded and customized locally
- Images are saved to the template directory as qcow2 files
- No Proxmox CLI tools (pvesh, qm) are required
- Status table shows only local columns (no Proxmox/VMID)
# Process only specific templates
./cloudbuilder.py --only debian-12,ubuntu-24-04
# Process all templates except specified ones
./cloudbuilder.py --except fedora-40,fedora-41
# Combine with other flags
./cloudbuilder.py --update --only debian-12Import templates from pre-built qcow2/img files without going through the customization process:
# Import templates from a local manifest file
./cloudbuilder.py --import-manifest imports.json
# Import templates from a remote manifest (URL)
./cloudbuilder.py --import-manifest http://example.com/imports.json
# Import only specific templates from manifest
./cloudbuilder.py --import-manifest imports.json --only rocky-9,centos-stream
# Force re-import templates that already exist (replaces them)
./cloudbuilder.py --import-manifest imports.json --forceManifest file format (imports.json):
{
"rocky-9": {
"source": "rocky-9.qcow2"
},
"centos-stream": {
"source": "centos-stream.qcow2",
"vmid": 9050
},
"custom-debian": {
"source": "custom-debian.qcow2",
"vmid": 9060,
"customize": true
}
}Manifest fields:
| Field | Required | Description |
|---|---|---|
source |
Yes | Filename, relative path, or full URL to the qcow2/img file. Relative sources are resolved against the manifest URL when importing from HTTP. |
vmid |
No | Specific VMID to assign (auto-assigns if omitted) |
customize |
No | Run virt-customize using config from templates.json (default: false) |
Generating a manifest from a directory:
# Generate manifest from template directory (default location)
./cloudbuilder.py --generate-manifest
# Generate manifest from a specific directory
./cloudbuilder.py --generate-manifest /path/to/images/
# Specify output file
./cloudbuilder.py --generate-manifest -o my-manifest.json
# Output to stdout (for piping)
./cloudbuilder.py --generate-manifest -o -
# Serve the directory with a simple HTTP server
cd /var/lib/cloudbuilder/templates && python3 -m http.server 8080
# Then import on another host - sources are resolved relative to manifest URL
./cloudbuilder.py --import-manifest http://myserver:8080/imports.json# Specify custom paths
./cloudbuilder.py --config /path/to/templates.json --template-dir /path/to/templates --temp-dir /path/to/tmp
# Specify Proxmox storage (overrides automatic detection)
./cloudbuilder.py --storage local-zfs
# Set minimum VMID
./cloudbuilder.py --min-vmid 9000| Option | Description |
|---|---|
--status |
Show template status without making changes |
--update |
Update existing templates |
--rebuild |
Rebuild templates from scratch |
--build-only |
Build templates locally without importing to Proxmox (standalone mode) |
--self-update |
Update cloudbuilder from git repository |
--setup-completions |
Set up shell autocompletions (bash/zsh) |
--import-manifest FILE/URL |
Import pre-built images from a manifest file or URL (JSON) |
--generate-manifest [DIR] |
Generate manifest JSON from a directory of qcow2/img files (default: template directory) |
--base-url URL |
Optional: prefix sources with full URL in generated manifest (by default, outputs just filenames which are resolved relative to manifest URL on import) |
-o, --output FILE |
Output file for generated manifest (default: imports.json, use '-' for stdout) |
--force |
Force operation: for imports, removes and re-imports existing templates; for --self-update, discards local changes |
--only LIST |
Process only specific templates (comma-separated) |
--except LIST |
Process all templates except specified ones (comma-separated) |
--config PATH |
Path to templates configuration file (default: templates.json) |
--storage NAME |
Storage location in Proxmox (if not specified, will auto-detect) |
--template-dir PATH |
Directory for storing templates (default: /var/lib/cloudbuilder/templates) |
--temp-dir PATH |
Base directory for temporary files (default: /var/lib/cloudbuilder/tmp) |
--log-dir PATH |
Directory for log files (default: /var/log/cloudbuilder) |
--min-vmid NUM |
Minimum VMID for templates (default: 9000) |
Cloudbuilder automatically detects and selects a compatible Proxmox storage for VM templates. It:
- Scans available storages in your Proxmox environment
- Identifies storages that support VM images (content types "images" or "rootdir")
- Selects the first compatible storage found
- Logs which storage was selected
You can override automatic detection by specifying a storage with --storage.
Cloudbuilder maintains metadata about templates in two places:
metadata.jsonin the template directory- Template notes in Proxmox (accessible via the web UI)
Example metadata.json:
{
"debian-12": {
"build_date": "2025-02-24 16:31:08",
"last_update": "2025-02-24 16:52:59",
"vmid": 9000
}
}cloudbuilder.py: Main entry point and orchestrationtemplate.py: Template models and managementproxmox.py: Proxmox integrationutils.py: Utilities and helpers
- Default mode: Ensures templates exist locally and in Proxmox
- Update mode: Updates existing templates while maintaining VMIDs
- Rebuild mode: Rebuilds templates from scratch while preserving VMIDs
- Minimal downtime: Templates are built locally first, then replaced in Proxmox one by one
Templates are prepared for cloning with these critical steps (handled in run_commands):
- SSH Host Keys: Deleted during build, regenerated on first VM boot
- Machine ID: Truncated (not deleted) so each VM gets a unique ID
- Cloud-init State: Cleared so cloud-init runs fresh on new VMs
See CLAUDE.md for detailed implementation notes.
You can test built templates locally using QEMU without importing to Proxmox. This is useful for verifying customizations before deployment.
# Set the template to test
IMAGE_FILE="/var/lib/cloudbuilder/templates/freebsd-15.qcow2"
# Create cloud-init data
mkdir -p /tmp/cidata
echo "instance-id: test-$(date +%s)" > /tmp/cidata/meta-data
cat > /tmp/cidata/user-data << 'EOF'
#cloud-config
hostname: freebsd-15
user: root
password: cloudbuilder
chpasswd: {expire: False}
ssh_pwauth: True
EOF
# Generate cloud-init ISO
genisoimage -output /tmp/cidata.iso -volid cidata -joliet -rock /tmp/cidata/
# Boot the template (console mode)
qemu-system-x86_64 -m 1024 \
-hda "$IMAGE_FILE" \
-cdrom /tmp/cidata.iso \
-nographic -serial mon:stdioNotes:
- Login with
root/cloudbuilder(or the default cloud user for the distro) - Press
Ctrl+AthenXto exit QEMU - The image is modified during boot - use a copy if you want to preserve the original
# Boot with port forwarding for SSH
qemu-system-x86_64 -m 1024 \
-hda "$IMAGE_FILE" \
-cdrom /tmp/cidata.iso \
-nographic -serial mon:stdio \
-net nic -net user,hostfwd=tcp::2222-:22
# In another terminal, SSH in:
ssh -p 2222 root@localhostqemu-system-x86_64(from qemu-system-x86 package)genisoimage(from genisoimage package)
Logs are written to both the console and cloudbuilder.log in the log directory.