cloud-init is the de facto standard for initialising cloud instances during their first boot. When you launch an instance and pass it a user-data script, it is cloud-init that interprets and runs it: it creates users, writes files, installs packages, mounts disks or starts services.
The ideal combination is clear: the golden AMI holds what does not change —operating system, runtime, hardening— and user-data supplies what varies by environment or instance: configuration, injected secrets, role. That way you reuse a single image across many contexts.
Two ways to write user-data
user-data accepts several formats; the two most common are the shell script and cloud-config.
- Shell script: starts with
#!/bin/bash. Simple and direct for quick tasks. - cloud-config: starts with
#cloud-configand uses declarative YAML. Cleaner, more readable and more idempotent for configuring users, packages, files and commands.
A cloud-config example
A typical #cloud-config declares sections such as packages: (packages to install),
write_files: (configuration files), runcmd: (final commands) and users: (accounts
and keys). Being declarative, it is easier to review and maintain than a long script.
Best practices
- Keep user-data small: if it grows too much, that probably belongs baked into the AMI.
- Idempotence: design the commands so that re-running them breaks nothing.
- Never put secrets in the clear in user-data: it is readable from the instance metadata. Inject them from Secrets Manager, Parameter Store or Vault at runtime.
- Protect metadata access: use IMDSv2 to mitigate credential theft via SSRF.
- Log and debug: the cloud-init logs (
/var/log/cloud-init-output.log) are your best friend when something fails.
Baking or booting: where each thing goes
| Goes in the AMI (baking) | Goes in user-data (booting) |
|---|---|
| Operating system and patches | Environment-specific configuration |
| Runtime, agents and hardening | Per-instance variables and parameters |
| Stable, heavy software | Cluster registration and discovery |
| Anything slow to install | Runtime secret injection |
Golden rule: what is stable and slow gets baked; what is variable and light goes at boot.
Common mistakes that cost hours
- Putting into user-data what should be in the image, which yields slow, fragile boots.
- Exposing secrets in plain text in the metadata.
- Assuming user-data re-runs on every boot: by default it only runs on the first one.
- Not checking cloud-init logs when the instance “does not do what it should”.
Frequently asked questions
Does user-data run on every reboot?
By default, only on the first boot. You can configure cloud-init to run certain parts on every boot, but do it deliberately and idempotently.
Is it safe to pass passwords in user-data?
No. user-data is readable from the instance metadata. Use a secrets manager and inject them at runtime, and protect the metadata with IMDSv2.
Does cloud-init only work on AWS?
No. cloud-init is cross-platform and works on AWS, Azure, GCP and others, which makes it ideal for automating boot in a portable way.
At imaxe.cloud we design images meant to be combined with cloud-init, so that a single AMI serves you across many scenarios.



