Why Your Script Failed
Cloud-init does not run as a single monolithic block. It runs in five distinct stages spread across the systemd boot sequence. Understanding this is essential for debugging race conditions — the most common being "my script tried to download a package before the network was ready."
Here is the sequence:
| Stage | systemd Unit | Network Available? | What Happens |
|---|---|---|---|
| Generator | (pre-unit) | No | Decides whether cloud-init should run at all this boot. Checks for /etc/cloud/cloud-init.disabled. |
| Local | cloud-init-local.service |
No | Finds the datasource. Configures local storage. Applies any network config that doesn't require a live network. |
| Network | cloud-init.service |
Yes (basic) | Fetches metadata and user-data from IMDS. Configures network interfaces. |
| Config | cloud-config.service |
Yes | Runs configuration modules: sets hostname, expands root partition, injects SSH keys, creates users. |
| Final | cloud-final.service |
Yes (fully online) | Runs runcmd, shell scripts, package installs, and configuration management tools. This is where your User Data executes. |
Practical implications:
- If you write a systemd unit that
Wants=network-online.targetand install it via User Data, it will work — because User Data runs in Final, after the network is fully established. - If you write a
cloud-boothook(a special header type), it runs even earlier than Local, before cloud-init has fetched anything. Use this only for tasks that genuinely must happen before any network configuration. - Package installation (
apt-get install) always happens in Final. You cannot install packages in the Config stage.
Checking stage timing on a running instance:
Each stage maps to its own systemd unit — cloud-init-local, cloud-init, cloud-config, and cloud-final — and their status shows whether each completed (active exited) or failed. All four reporting success means cloud-init ran the full sequence; any one reporting failed pinpoints which stage broke.