Running via systemd¶
cloud-init-pve doesn't do anything at boot on its own — something has to
invoke it. contrib/systemd/
has two example units for that, split around the one thing in a
cloud-init-pve pass that actually needs a network: the package upgrade.
Why two units¶
Everything except the package upgrade — renaming/addressing network interfaces, setting the hostname, creating the user and its SSH keys — is local-only and should run as early in boot as possible, ideally before systemd-networkd itself starts, so the interface config it writes is picked up on networkd's first start rather than needing a reload.
The package upgrade is the opposite: it needs a working, resolvable network
connection, which plainly isn't available yet at that point in boot, and it
can be slow — running apt-get dist-upgrade inline with the rest would
either fail (no network yet) or hold up reaching a login prompt (if ordered
later, but still required by boot-critical targets).
cloud-init-pve's -stage flag splits these two concerns so they can run
as separate, independently-ordered systemd units:
| Stage | Flag | Does | Needs |
|---|---|---|---|
| boot | -stage=boot |
network, FQDN, user | nothing (local only) |
| final | -stage=final |
package upgrade | a working network |
| both | (no flag) | everything above | — |
Run with no -stage flag (the default) for a one-shot manual invocation —
it does both in sequence, same as before this split existed.
The example units¶
cloud-init-pve.service—-stage=boot, orderedBefore=systemd-networkd.service.cloud-init-pve-final.service—-stage=final, orderedAfter=network-online.target, onlyWants-ed (notRequires-d) bymulti-user.targetso a slow or failed upgrade never blocks reaching a login prompt.
The .deb release installs both to /lib/systemd/system/ and enables
them on install (systemctl enable, no --now) — so they're wired up to
run on the next boot, but nothing starts immediately as a side effect of
apt install. That matters when this package gets installed as part of
building a VM template: the template's chroot install won't itself trigger
a cloud-init-pve run, only the template's eventual real first boot will.
Removing the package disables both units again first, so no dangling
enable symlinks are left behind. If you don't want them enabled at all
(e.g. you're not ready to review them yet), disable after install:
systemctl disable cloud-init-pve.service cloud-init-pve-final.service
Building from source, or using the .tar.gz release, install either (or
both) by copying to /etc/systemd/system/ and enabling manually:
systemctl enable cloud-init-pve.service cloud-init-pve-final.service
See the comments in each file for the full reasoning behind its ordering, including a caveat about interface renaming racing against very early driver loading.
How the split stays safe across reboots¶
Both fqdn and package_upgrade are only honored on the guest's first
boot (see Cloud-Init Data) — but "first boot" means
two different things for the two stages once they're split across separate
processes:
- The boot stage's own run creates
cloud-init-pve's state file. By the time the final stage runs later that same boot, the state file already exists. - If "first run" just meant "state file doesn't exist yet", the final stage would see a state file (written moments earlier by the boot stage) and conclude it's not the first boot — and skip the package upgrade entirely, even on the actual first boot.
So the package upgrade tracks its own package_upgrade_applied flag in the
state file, set only once the upgrade has actually run, independently of
whether the state file exists at all. The boot stage never touches it.