A universal builder for generating NoCloud ISO images. Works as a regular ISO — attach it as a CD-ROM to a VM and cloud-init picks up the configuration automatically.
Proxmox's built-in cloud-init is limited: user, password, DNS, IP, SSH key, and package upgrade. Real-world provisioning needs more: custom packages, files, commands, multiple network interfaces, routing policies, bonds, VLANs.
This builder generates a standard ISO with cidata label that cloud-init auto-detects. Proxmox sees it as an ordinary CD-ROM.
- JSON configuration — explicit, unambiguous
- Modular builds — split presets across
main.jsonandconf.d/*.json - Multi-ID conflict resolution — compose setups by ID, override by priority
- Include system — ship static assets (configs, binaries) alongside scripts
- Multiple network interfaces — DHCP, static, bonds, bridges, VLANs
- Binary-safe includes — base64-encoded for exact byte reproduction
- Smart missing ID detection — checks both scripts and includes
- Network v1/v2 — unified
interfacesformat; v1 (universal) or v2 (Netplan) - Postbuild — auto-deploy after ISO creation (global, per-build, or CLI)
- Auto-clean output directory
- CLI without config — inline builds with full param coverage
- Config as JSON string — pass entire config via
--config-json - Real .sh scripts inside ISO — readable, debuggable, version-controllable
- Robust execution —
set -e+ logging for every script
# Install dependencies
apt-get install -y python3-yaml genisoimage # Debian/Ubuntu/Proxmox
dnf install -y python3-pyyaml genisoimage # RHEL/Rocky/Alma
pacman -S python-yaml cdrtools # Arch
# Build everything
python3 build.py -a
# Build a specific preset
python3 build.py --preset ubuntu-router
# Build multiple presets
python3 build.py --preset ubuntu-router,debian-web
# Build from a specific conf.d directory
python3 build.py -a --conf-d-build conf.d/production
# Inline build without any config file
python3 build.py --no-config \
--profile ubuntu --ids default \
--hostname myvm --ip 10.0.0.10/24 \
--gateway 10.0.0.1cibuilder/
├── build.py # CLI frontend
├── lib/ # Core library
│ ├── builder.py # Build orchestration
│ ├── config.py # Config loading + conf.d merging
│ ├── scripts.py # Script resolution + includes
│ ├── network.py # Network config generation
│ └── iso.py # ISO creation + YAML writing
├── main.json # Orchestrator: global + builds
├── conf.d/ # Modular build definitions
│ └── *.json
├── samples/ # Provisioning scripts + includes
│ ├── ubuntu/ # Profile: Ubuntu-based machines
│ │ ├── 01-base_default.sh
│ │ ├── 10-network_default.sh
│ │ └── include/
│ │ ├── default_motd
│ │ └── router_iptables.conf
│ ├── debian/ # Profile: Debian-based machines
│ ├── alpine/ # Profile: Alpine-based machines
│ ├── rhel/ # Profile: RHEL-based machines
│ └── common/ # Shared across all profiles
├── postbuild/
│ └── deploy-to-proxmox.sh
├── docs/
│ ├── usage.md # User guide
│ └── tech.md # Technical documentation
└── output/ # Generated ISOs and files
{
"global": {
"output_dir": "./output",
"iso_label": "cidata",
"default_ssh_key": "~/.ssh/id_rsa.pub",
"auto_clean": true,
"postbuild": "postbuild/deploy-all.sh",
"params": {
"timezone": "UTC",
"dns": "8.8.8.8",
"domain": "local"
}
},
"builds": [
{
"preset": "ubuntu-router",
"profile": "ubuntu",
"ids": ["default", "router"],
"params": {
"hostname": "router1",
"ip": "dhcp",
"interface": "ens18"
}
},
{
"preset": "ubuntu-web",
"profile": "ubuntu",
"ids": ["default"],
"params": {
"hostname": "web-01",
"ciuser": "admin",
"password": "changeme123",
"ip": "10.0.0.10/24",
"gateway": "10.0.0.1",
"interface": "ens18"
}
}
]
}// conf.d/routers.json
{
"global": {
"params": {
"timezone": "Europe/Moscow"
}
},
"builds": [
{
"preset": "router-east",
"profile": "ubuntu",
"ids": ["default", "router"],
"params": {
"hostname": "router-east",
"ip": "dhcp"
}
}
]
}{priority}-{name}_{id}.sh
01-disk_default.sh→ priority=1, name=disk, id=default10-network_router.sh→ priority=10, name=network, id=router
When multiple scripts share a priority, the one whose id appears later in the build's ids array wins.
samples/{profile}/include/{id}_{name}
samples/ubuntu/include/
default_motd → /opt/provision/include/motd
router_iptables.conf → /opt/provision/include/iptables.conf
Include files are base64-encoded in user-data to ensure binary files are reproduced exactly.
# Show help
python3 build.py
# Build all
python3 build.py -a
# Pass config as JSON string
python3 build.py --config-json '{"builds":[{"preset":"web","profile":"ubuntu","ids":["default"],"params":{"hostname":"web1"}}]}'
# Ignore existing main.json
python3 build.py --no-config --profile ubuntu --ids default --hostname test
# Build from specific conf.d
python3 build.py -a --conf-d-build conf.d/production# Specific preset
python3 build.py --preset ubuntu-router
# Multiple presets
python3 build.py --preset ubuntu-router,debian-db
# Filter by profile
python3 build.py -a --profile ubuntu,debian
# Override IDs
python3 build.py -a --ids default,router
# Preview
python3 build.py -a --dry-run
# List presets
python3 build.py --list# Minimal
python3 build.py --profile ubuntu --ids default \
--hostname myvm --ip 10.0.0.10/24 --gateway 10.0.0.1
# Full inline
python3 build.py --no-config \
--profile ubuntu --ids default \
--hostname router --ip dhcp \
--ciuser admin --password changeme \
--packages nginx,iptables-persistent \
--timezone Europe/Moscow \
--growpart --package-update \
--script ./custom-setup.sh
# Multiple interfaces
python3 build.py --profile ubuntu --ids default \
--hostname router \
--interfaces '[{"name":"ens18","type":"dhcp"},{"name":"ens19","type":"static","address":"192.168.50.10","netmask":"255.255.255.0"}]'# Files only
python3 build.py -a --no-iso
# Skip auto-clean
python3 build.py -a --no-clean
# Skip postbuild
python3 build.py -a --no-postbuild
# Custom postbuild
python3 build.py -a --postbuild postbuild/deploy-to-proxmox.sh
# Verbose
python3 build.py -a -v
# Quiet
python3 build.py -a -qqm set <vmid> --ide2 local:iso/{preset-name}.iso,media=cdrom
qm set <vmid> --sata0 local:iso/{preset-name}.iso,media=cdromVia GUI: Hardware → Add → CD/DVD Drive → select ISO.
docs/usage.md— User guidedocs/tech.md— Technical docs
| Profile | Init | Packages | Network | Support |
|---|---|---|---|---|
| Ubuntu/Debian | systemd | apt | systemd-networkd/Netplan | v1 + v2 |
| Rocky/Alma/RHEL | systemd | dnf/yum | NetworkManager | v1 |
| Alpine | OpenRC | apk | ifupdown | v1 |
| Arch | systemd | pacman | systemd-networkd | v1 + v2 |
Debian / Ubuntu / Proxmox:
apt-get install -y python3-yaml genisoimageRHEL / Rocky / AlmaLinux:
dnf install -y python3-pyyaml genisoimageArch Linux:
pacman -S python-yaml cdrtoolsNixOS:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [ pkgs.python3 pkgs.python3Packages.pyyaml pkgs.cdrkit ];
}MIT