π Note: This repository was split out from the Straypenguins-Tips-Inventory collection (original path: vSphere/cloudinit-vm-deploy). The project grew large enough to justify its own repository; historical copies and prior context remain in the original repository's commit history.
If you arrived here from the Tips Inventory, welcome β this repository now contains the full cloudinit-vm-deploy kit (sources, templates, examples, and documentation). Continue below for the project Overview and usage instructions.
This kit is designed to enable quick and reproducible deployment of Linux VMs from a well-prepared (not an out-of-the-box default) VM Template on vSphere, using the cloud-init framework. The main control program is a PowerShell script: cloudinit-linux-vm-deploy.ps1. The workflow is split into four phases:
- Phase 1: Create a clone from a VM Template
- Phase 2: Prepare the clone to accept cloud-init
- Phase 3: Generate a cloud-init seed (user-data, meta-data, optional network-config), pack them into an ISO, upload it to a datastore and attach it to the clone's CD drive, then boot the VM and wait for cloud-init to complete
- Phase 4: Detach and remove the seed ISO from the datastore, then place
/etc/cloud/cloud-init.disabledon the guest to prevent future automatic personalization (can be skipped with-NoCloudReset)
π Note β DiskOnly mode
The primary workflow remains template β clone β initialization β personalization. In addition, this kit provides an optional extension called DiskOnly Mode that lets you reapply only disk expansion (partition/filesystem/swap) on VMs previously deployed with this kit. See DiskOnly Reapply Mode for details.
Between cloudinit-linux-vm-deploy.ps1 versions 0.1.5 and 0.1.7, updates were introduced to the parameter and seedβtemplate formats (multi-user support, per-user SSH key placement, DNS nameserver handling, and a consolidated swaps mapping). When your main script version crosses this range, you must upgrade the parameter files and seed template YAMLs in lockstep with cloudinit-linux-vm-deploy.ps1. To verify that they are coordinated correctly, validate the generated seed files:
- Run the script with Phase 3 only and with
-NoRestartto produce the seed files without reapplying personalization to a VM:.\cloudinit-linux-vm-deploy.ps1 -Phase 3 -Config .\params\<your_params>.yaml -NoRestart
- Inspect
spool/<new_vm_name>/cloudinit-seed/user-dataandnetwork-configto confirm formatting and indentation.
See UPGRADE_NOTES_PARAM_FORMAT_CHANGE.md in this directory for detailed notes and examples.
π Table of contents
- Overview
- Key Points β What This Kit Complements in cloud-init
- Key Files
- Requirements and Pre-setup (admin host and template VM)
- Quick Start
- Phases β What Does Each Step Perform?
- Template Infra: What is Changed and Why
- DiskOnly Reapply Mode
- mkisofs / ISO Creation Notes
- Operational Recommendations
- Troubleshooting (common cases)
- Logs & Debugging
- References
- License
This kit assumes the lifecycle: template β new clone β initialization β personalization. It complements cloud-init by addressing several practical operational gaps in vSphere deployments:
- Filesystem expansion beyond root: kit-specific handling to reformat/recreate swap and to expand non-root ext2/3/4 filesystems after virtual disk enlargement (LVM is not handled).
- NetworkManager adjustments for Ethernet connection profiles (for example: disable IPv6, set
ignore-auto-routes/ignore-auto-dnswithnmcli). - Deterministic
/etc/hostspopulation while settingmanage_etc_hosts: falsefor cloud-init. - Template safety: the template VM is configured to avoid accidental cloud-init runs; clones remove that protection during initialization.
- Admin-host-driven seed ISO lifecycle: generate seed files, create a
cidataISO, upload it to a datastore, attach it to the VM, and poll until cloud-init completes (quick-check + completion polling). - The script stores logs and generated artifacts under
spool/<new_vm_name>/on the admin host for auditing and troubleshooting. - Use PowerShell
-Verboseto print detailed internal steps for debugging.
This kit is intended for the template β clone β initialization β personalization workflow. It is not meant as a general-purpose tool to retrofit cloud-init onto arbitrary running production VMs.
But note there is an exception; the optional DiskOnly Reapply Mode is provided for safely performing disk-only expansions on VMs originally deployed with this kit.
cloudinit-linux-vm-deploy.ps1β main PowerShell deployment script (implements Phases 1β4)params/vm-settings_example.yamlβ example parameter file (copy and edit per VM)templates/original/*_template.yamlβ cloud-inituser-data,meta-data, andnetwork-configtemplates (copy totemplates/and edit as needed)scripts/init-vm-cloudinit.shβ script copied to the clone and run in Phase 2 to clear template artifacts and re-enable cloud-init on the cloneinfra/prevent-cloud-init.shβ helper to install template infra files and create/etc/cloud/cloud-init.disabledon the templateinfra/cloud.cfg,infra/99-template-maint.cfgβ template-optimized cloud-init configuration files (only intentionally changed parameters are annotated in the shipped files)infra/enable-cloudinit-service.shβ helper to ensure cloud-init services are enabled (rarely used)infra/req-pkg-cloudinit.txt,infra/req-pkg-cloudinit-full.txtβ recommended package lists for the template VMspool/β repository includes aspool/directory (contains a dummy file so the folder exists after clone/unzip). At runtime the script createsspool/<new_vm_name>/for logs and generated files.
- Windows PowerShell (5.1+) or PowerShell Core on Windows
- VMware PowerCLI (e.g., VMware.VimAutomation.Core)
powershell-yamlmodule for YAML parsing- ISO creation tool: Win32
mkisofs.exe(the script defaults to a Win32 mkisofs from cdrtfe). Adjust$mkisofsand$mkArgsin the script if you use a different tool. - Clone or unzip this repository on the admin host. The repo contains a
spool/directory (dummy file present), which the script expects to exist.
- This kit assumes the template is a well-prepared VM that you have tailored as a base for cloning (this kit does not provide one).
It may consist of considerable minimal resources, e.g., 2 CPUs, 2.1GB memory, 8GB primary disk, 2GB swap / 500MB kdump disks with 'Thin' vmdk format, all of which can be automatically expanded by the capabilities of cloud-init and the kit during provisioning. open-vm-toolsinstalled and running; required for guest operations such asCopy-VMGuestFile/Invoke-VMScripton the VMs cloned from this template (it should normally inherit working).cloud-initandcloud-utils-growpartinstalled (optionallydracut-config-genericif you rebuild initramfs)- A CD/DVD device configured on the VM (seed ISO must be attached to the guest's CD drive)
- Copy
infra/to the template and runprevent-cloud-init.shas root to install infra files and create/etc/cloud/cloud-init.disabled - Provide valid guest credentials for inβguest operations: Define at least one user in the parameter file (typically as
user1) and mark the intended account withprimary: true. The selected primary userβs credentials are used for inβguest actions such asInvoke-VMScriptand other guest API calls. The account must be a real, local-login-capable administrative user on the Template VM (able to log in via the VM console) and must be able to runsudo /bin/bashwithout an interactive password prompt (for example via an appropriateNOPASSWD:sudoers rule).
π For a short checklist and examples of the parameter/template format, see Caution: Parameter and template format changes.
π Notes and limitations:
- Partition expansion: the partition(s) you intend to expand must be the last partition on the disk; otherwise the kit's non-LVM expansion helpers cannot extend them. For maximum flexibility, we recommend placing swap, kdump, and any other volumes that may need to be expanded (for example /opt or /u01) on separate, dedicated VMDKs.
- Supported filesystems for kit-managed expansion:
ext2,ext3,ext4, andswap. LVM-managed volumes are not supported. - Line endings: PowerShell scripts and
params/*.yamlshould use CRLF (Windows). Guest shell scripts and cloud-init templates must use LF (Unix).
-
Clone or unzip this repo on the Windows admin host and install PowerCLI and
powershell-yaml. -
On the template VM:
- Copy
infra/into the template filesystem and run:This installs the kit-optimizedcd infra sudo ./prevent-cloud-init.sh/etc/cloud/cloud.cfg,/etc/cloud/cloud.cfg.d/99-template-maint.cfg, and/etc/cloud/cloud-init.disabled. - Shut down the VM and convert it to a vSphere Template.
- Copy
-
On the admin host:
- Copy
params/vm-settings_example.yamlto a new filename (e.g.,params/vm-settings_myvm01.yaml) and edit it.
π‘Tip: include the target VM name (new_vm_nameinparams) in the filename for clarity. - Copy
templates/original/*_template.yamltotemplates/and update them as needed (especially network device names innetwork-config_template.yaml).
- Copy
-
Run the deploy script from the repository root:
.\cloudinit-linux-vm-deploy.ps1 -Phase 1,2,3 -Config .\params\vm-settings_myvm01.yaml
- You may run a single phase (e.g.,
-Phase 1) or a contiguous sequence (e.g.,-Phase 1,2,3). Non-contiguous lists (e.g.,-Phase 1,3) are rejected. - The script may prompt when
-NoRestartconflicts with a requested multi-phase run; follow the prompts.
- You may run a single phase (e.g.,
-
Inspect
spool/<new_vm_name>/for logs and generated artifacts. Primary log:spool/<new_vm_name>/deploy-YYYYMMDD.log. Seed files are underspool/<new_vm_name>/cloudinit-seed/and the seed ISO isspool/<new_vm_name>/cloudinit-linux-seed.iso.
π Refer to the script's help for details:
Get-Help ./cloudinit-linux-vm-deploy.ps1 -DetailedβImportant: Phase selection must be a contiguous ascending list (single phase is allowed). Examples:
- Valid:
-Phase 1or-Phase 1,2,3 - Invalid:
-Phase 1,3
Phase 1β3 form the main deployment flow. Phase 4 is a post-processing/finalization step and is recommended to be run after confirming Phase-3 succeeded.
ββββββββββββββββββββββββββββββββ
Purpose:
- Create a new VM by cloning the VM Template and apply specified vSphere-level hardware settings (CPU, memory, disk sizes, disk storage format). This phase does not perform guest power-on or shutdown.
High-level steps:
- Validate no VM name collision.
- Resolve resource pool / datastore / host / portgroup from parameters.
- Perform
New-VMto clone the template. - Apply CPU and memory settings (
Set-VM). - Resize virtual disks as specified in
params.disks(viaSet-HardDisk).
Result:
- A new VM object is created in vCenter (not yet powered on).
Cautions / Notes:
- Do not run if a VM with the same name already exists β the script will abort.
- This kit is not intended to retrofit cloud-init onto arbitrary running VMs; use the template β clone path.
- For disk resizing to work, each 'name' property in
params.disksmust exactly match the VM's virtual disk Name as shown in vSphere (for example: "Hard disk 1"). If the name does not match,Set-HardDiskwill not find the disk and resizing will fail.
ββββββββββββββββββββββββββββββββ
Purpose:
- Run guest-side initialization to remove template protections and clear cloud-init residual state. The VM is left powered on when Phase 2 completes so administrators can log in for verification or adjustments.
High-level steps:
- Power on the VM (the script respects
-NoRestartsemantics and will prompt if conflicts arise). - Ensure a working directory on the guest and copy
scripts/init-vm-cloudinit.shto the VM. - Execute the init script, which:
- Cleans subscription-manager state (RHEL specific).
- Deletes existing NetworkManager Ethernet connection profiles (Ethernet only).
- Runs
cloud-init clean. - Truncates
/etc/machine-id. - Removes
/etc/cloud/cloud-init.disabledand/etc/cloud/cloud.cfg.d/99-template-maint.cfg. - Writes
/etc/cloud/cloud.cfg.d/99-override.cfgwithpreserve_hostname: falseandmanage_etc_hosts: false.
- Remove the transfer script from the guest.
Result:
- The clone is prepared to accept cloud-init personalization; the VM remains powered on.
Cautions / Notes:
- The included
scripts/init-vm-cloudinit.shtargets RHEL-like systems; verify and adapt it for other distributions. - Because the VM remains powered on after Phase 2, avoid rebooting it before attaching the seed ISO in Phase 3 (unless you intend to boot with the seed attached); an unexpected boot may trigger cloud-init without the intended seed.
ββββββββββββββββββββββββββββββββ
Purpose:
- Render
user-data,meta-data, and optionalnetwork-configfrom YAML templates and parameters, create acidataISO, upload it to the datastore, attach it to the VM CD drive, boot the VM, and wait for cloud-init to complete. The VM is left powered on when Phase 3 finishes.
High-level steps:
- Shut down the VM (unless
-NoRestartis requested and accepted) if it is not already powered off. - Ensure a local seed working directory and render
user-data,meta-data, and (if present)network-configfrom the templates, replacing placeholders fromparams. Foruser-data, the script may inject a kit-specificruncmdblock to:- Run
resize2fson specified non-root partitions. - Reinitialize and resize swap devices with careful UUID updates to
/etc/fstab. - Modify NetworkManager Ethernet connection profiles with
nmcli(ignore-auto-routes,ignore-auto-dns, IPv6 disablement).
- Run
- Create a
cidataISO usingmkisofsand place it inspool/<new_vm_name>/. - Upload the ISO to the datastore path specified by
params.seed_iso_copy_storeand attach it to the VM's CD drive. The script will abort if an ISO with the same name already exists at the target path. - Power on the VM and detect cloud-init activity:
- Run a
quick-checkscript (one-shot) on the guest to detect early evidence that cloud-init activated after the ISO attach. - If quick-check indicates possible activity, copy a
check-cloud-initscript and poll until it reports completion (or timeout). Temporary helper scripts are removed from the guest; local copies, too.
- Run a
Result:
- cloud-init has applied the personalization and the VM is ready; VM remains powered on.
Cautions / Notes:
/etc/hostsis completely overwritten by the template's entries. If you need extra static host records, add them to thewrite_files > contentsection oftemplates/user-data_template.yamlbefore running Phase 3.- If
/etc/cloud/cloud-init.disabledexists on the guest, Phase 3 cannot apply the seed β the script checks for this file and aborts when it can be detected. If the VM is powered off at the start of Phase 3 the script cannot probe the file and will continue; in that case cloudβinit may not be applied in the run; manual intervention may be required. - If
-NoRestartprevents the required pre-shutdown (and therefore the boot at the end of this phase), the clone will not automatically apply the cloudβinit personalization even though the seed ISO is attached. Phase 3 will emit a warning in that case; a manual reboot is required to apply the modification. - Re-running Phase 3 repeatedly without finalizing with Phase 4 can cause undesired side effects, for example repeated SSH hostβkey regeneration and duplicated NetworkManager connection profiles. If you must retry Phase 3 after a partial failure, re-run Phase 2 (guest initialization) first to mitigate negative effects; note that filesystem expansions already applied will not be reprocessed and user duplication will not occur. Always run Phase 4 once you are satisfied.
ββββββββββββββββββββββββββββββββ
Purpose:
- Detach the seed ISO, remove the ISO file from the datastore, and (by default) create
/etc/cloud/cloud-init.disabledon the guest to prevent future automatic cloud-init runs. If-NoCloudResetis supplied, the script detaches and deletes the ISO but skips creating/etc/cloud/cloud-init.disabled.
High-level steps:
- Detach the CD/DVD media from the VM.
- Remove the uploaded ISO file from the datastore (via the vmstore path).
- Unless
-NoCloudResetis set, use guest operations to create/etc/cloud/cloud-init.disabledon the guest.
Result:
- The seed ISO is removed and cloud-init is disabled for future boots (unless skipped).
Cautions / Notes:
- Phase 4 does not attempt to power on the VM. If the VM is powered off or VMware Tools are not running, the script cannot create
/etc/cloud/cloud-init.disabledand will exit with an error; run Phase 4 when the VM is powered on or use-NoCloudResetif you only need to remove the ISO. - If detaching media triggers a confirmation prompt in the vSphere UI (VMRC or vSphere Client), you may need to confirm manually for the operation to complete.
Files in infra/ (cloud.cfg, 99-template-maint.cfg) are tuned to make the template safe for cloning. The shipped infra/cloud.cfg is based on RHEL9 defaults; only intentionally changed parameters are annotated with [CHANGED]. Key intentional changes include:
users: []β suppress automatic creation of the default cloud user (e.g.,cloud-user) β [CHANGED]disable_root: falseβ prevent cloud-init from creatingsshd_config.d/50-cloud-init.conffile. This strategy assues the template VM has been properly configuredsshd_configorsshd_config.d/80-custom.cfg, etc. (adjust per policy) β [CHANGED]preserve_hostname: trueβ preserve the template hostname (clones receive99-override.cfgto setpreserve_hostname: false) β [CHANGED]- Set 'frequecy' of many cloud-init modules to
once/once-per-instanceto avoid repeated execution on templates and clones β [CHANGED] - Removed package update/upgrade from cloud-final to avoid unintended package changes on the template and during clone personalization β [CHANGED]
πNotes:
- SSH host key regeneration settings (e.g.,
ssh_deletekeys,ssh_genkeytypes) are left at the distro defaults and are not intentionally changed by this kit.
The DiskOnly reapply mode, introduced in cloudinit-linux-vm-deploy.ps1 (v0.1.8), is an auxiliary flow that provides a convenient, lowβrisk way to perform only disk expansion (partition, filesystem, and swap resizing) on VMs originally deployed with this kit via the full template β clone β initialization β personalization workflow.
- Typically, expanding partitions requires manually removing and recreating partitions, running
fsckandresize2fs, reformatting swap and editing/etc/fstab, etc. If the partition being expanded is the root filesystem, the process often also requires booting the VM from rescue media to perform the work offline. DiskOnly leverages cloud-init to perform these operations safely and automatically at the appropriate boot time, reducing risk and allowing lessβexperienced operators to perform the task reliably. - This feature is an auxiliary path and does not replace the kitβs normal full-deploy workflow. It is not intended as a general-purpose configuration-change mechanism for arbitrary VMs, which may be missing required cloud-init components or the template-derived configuration under
/etc/cloud. - As with regular partition operations, only partitions that are at the end of the disk can be expanded.
DiskOnly mode is designed to suppress the usual cloud-init effects such as user creation and network changes, and to run only the cloud-init modules required for disk operations. To work correctly, the kit must include the appropriate parameter file, seed YAML, and scripts in the tree.
A reapply is triggered by a different cloud-init instance_id than the previous deployment. For DiskOnly mode, ensure the instance_id in your parameter file has never been used in any previous deployment (for example, append or replace a date suffix at least).
Alternatively, you can clear the previous cloud-init instance_id along with cache data by running cloud-init clean, if this data is not critical in your environment.
Conversely, except for the core disk-related parameters (resize_fs, swaps), other values such as hostname should remain the same as the current guest settings.
π¨ Warning:
Always test in a non-production environment first (use VM snapshots). See the general operational cautions elsewhere in this README.
-
On vSphere, expand the target VMDK(s) of the VM. Disk expansion will not be triggered unless there is available space at the device level.
-
Copy the DiskOnly sample parameter file
params/original/vm-settings_reapply_diskonly_example.yamlintoparams/and edit it for your environment. You may rename the file (for examplevm-settings_reapply_diskonly_myvm01.yaml).π Set the
instance_idin your parameter file to a value that has never been used in any previous deployment fot this VM. -
Run the kit in DiskOnly mode; this mode never requires Phase-1. Example:
.\cloudinit-linux-vm-deploy.ps1 -Config params/vm-settings_reapply_diskonly_myvm01.yaml -DiskOnly -Phase 2,3
-
Phaseβ2 copies and executes
scripts/init-vm-cloudinit-diskonly.shon the guest. That script first creates an archive backup of existing/etc/cloud*and/var/log/cloud*.logunder/root/cloudinit-backup/, removescloud-init.disabledmarker if present, and installs the DiskOnly-specificcloud.cfgandcloud.cfg.d/99-override.cfg(embedded in the script). -
Phaseβ3 generates a seed ISO from
user-data_diskonly_template.yamlandmeta-data_template.yamland applies it;growpartandresizefs(and anyruncmdfor swap reinitialization) run under the DiskOnly configuration. -
Check the VM state and logs as needed before proceeding to the next phase.
-
-
Detach the seed ISO and recreate
cloud-init.disalbledmarker to deactivate cloud-init by running Phase-4. Example:.\cloudinit-linux-vm-deploy.ps1 -Config params/vm-settings_reapply_diskonly_myvm01.yaml -Phase 4
Note: The
-DiskOnlyoption has no effect in Phase 4 and can be omitted.
scripts/init-vm-cloudinit-diskonly.shβ DiskOnly preparation script executed on the guest by Phaseβ2 (backs up existing cloud config, removescloud-init.disabledmarker, and installs the DiskOnlycloud.cfgand override config entries. The cloud configs tuned for DiskOnly operation is embedded in the script.templates/original/user-data_diskonly_template.yamlβ DiskOnly user-data template; copy it intotemplates/prior to running Phase-3.params/vm-settings_reapply_diskonly_example.yamlβ Example parameter file to copy and edit for your run.
- The script's default
$mkisofspoints to a Win32mkisofs.exefrom the cdrtfe distribution. If you use a different ISO tool (for examplegenisoimageunder WSL), update variables$mkisofs(global) and$mkArgs(Phase-3 local) in the script. - The ISO must be labeled
cidataand includeuser-dataandmeta-dataat its root (and optionallynetwork-config) so cloud-init recognizes it. - Different mkisofs implementations accept different flags (Joliet, Rock Ridge, encoding). If ISO creation fails, verify the
$mkisofspath and the$mkArgsused in the script.
- Phase selection: use contiguous sequences only. Single-phase runs are supported; non-contiguous lists are rejected.
- Prefer running Phase 4 as a separate finalization step after confirming Phase 3 succeeded.
- VMware Tools: ensure
open-vm-toolsis installed and enabled on the Template VM; because clones are made from the Template they will normally inherit working VMware Tools immediately after cloning, which allows the guest operations used in Phases 2β4 to run without additional setup in standard environments. - Credentials: example
params/*.yamlfiles contain plain-text passwords for convenience. Treat them as sensitive and use secure credential storage in production. spooldirectory: the repository includes aspool/directory (dummy file present) so it exists after clone/unzip. The script createsspool/<new_vm_name>/and writesdeploy-YYYYMMDD.log, generated seed files undercloudinit-seed/, and the seed ISOcloudinit-linux-seed.isothere.
-
cloud-init did not run:
- Confirm
/etc/cloud/cloud-init.disabledwas removed on the clone (Phase 2 must have succeeded). - Inspect
spool/<new_vm_name>/cloudinit-seed/for the generateduser-data,meta-data, andnetwork-config, and check timestamps; also checkspool/<new_vm_name>/cloudinit-linux-seed.iso(mount or extract to verify contents). - Verify VMware Tools are running; without them
Copy-VMGuestFileandInvoke-VMScriptwill fail. - Check guest logs:
/var/log/cloud-init.log,/var/log/cloud-init-output.log, and/var/lib/cloud/instance/*.
- Confirm
-
ISO creation / upload failure:
$mkisofsnot found or incompatible flags. Update$mkisofsand$mkArgsin the script.seed_iso_copy_storeparameter malformed. Expected format:[DATASTORE] path(for example[COMMSTORE01] cloudinit-iso/). Trailing slash is optional.- A file with the same ISO name already exists at the datastore path (common when re-running Phase 3). Remedy: run Phase 4 alone (use
-NoCloudResetto avoid creating/etc/cloud/cloud-init.disabled) to remove the ISO, or delete it manually in vSphere.
-
Network configuration not applied:
- Verify
templates/network-config_template.yamlplaceholders andparams:netifN.netdevvalues match the guest's actual interface names (e.g.,ens192). Also check vSphere NIC ordering vs. guest device naming if your environment renumbers devices.
- Verify
-
Disk resizing did not occur (VMDK / partition / filesystem not grown)
- Check that each
params.disks[].nameexactly matches the VM's virtual disk Name as shown in vSphere (for example: "Hard disk 1"). If the name does not match,Set-HardDiskcannot find the disk and resizing will fail.
Quick verification (PowerCLI):
Get-HardDisk -VM <template-or-vm> | Select-Object Name, Filename, CapacityGB
Confirm the Name values match yourparamsand whether CapacityGB was actually changed. - Ensure the partition you intend to grow is the last partition on that disk; this kit's nonβLVM helpers cannot expand non-last partitions.
- If the disk Name and partition placement are correct but the guest size is unchanged, check the admin-host log
spool/<new_vm_name>/deploy-YYYYMMDD.logforSet-HardDiskmessages and errors. - For filesystem-level resizing, this kit supports ext2/3/4 and swap; XFS and LVM-managed volumes are not supported.
- Check that each
- Logs and generated artifacts are written to
spool/<new_vm_name>/on the admin host. The primary log isspool/<new_vm_name>/deploy-YYYYMMDD.log. Seed YAMLs are underspool/<new_vm_name>/cloudinit-seed/and the ISO isspool/<new_vm_name>/cloudinit-linux-seed.iso. - Run the script with
-Verboseto print additional internal steps to the console for debugging.
This project is licensed under the MIT License β see the repository LICENSE file for details.