| name | linux-cloud-init |
| description | Use when authoring, validating, or debugging cloud-init user-data, Ubuntu autoinstall, or RHEL-family Kickstart for first-boot provisioning. Use linux-server-provisioning for interactive post-boot setup and linux-config-management for ongoing desired state. |
| license | MIT |
| metadata | {"portable":true,"compatible_with":["claude-code","codex"],"author":"Peter Bamuhigire","author_url":"techguypeter.com","author_contact":"+256784464178"} |
Linux cloud-init
Distro support
Two-family skill — but mind the distinction:
- cloud-init
user-data / cloud-config is portable — the same file runs on
Ubuntu and Fedora/RHEL cloud images.
- OS-install automation is NOT portable — Ubuntu uses autoinstall
(subiquity); the RHEL family (Fedora, RHEL, CentOS Stream, Rocky, Alma,
Oracle) uses Kickstart (Anaconda). See
references/kickstart-reference.md.
| Concept | Debian/Ubuntu | RHEL family |
|---|
| Install automation | autoinstall (subiquity) | Kickstart (Anaconda) |
| Install config | autoinstall: schema | .ks directives, inst.ks= boot arg |
| First-boot config | cloud-init user-data | cloud-init user-data (same) |
| Admin group in cloud-config | sudo | wheel |
| Default cloud user | ubuntu | fedora / cloud-user / ec2-user |
| Time pkg in cloud-config | systemd-timesyncd | chrony |
| Network rendering | cloud-init → Netplan | cloud-init → NetworkManager |
Write distro-neutral cloud-config where possible (use package_update,
network-config v2, and group wheel on RHEL). See
references/kickstart-reference.md and
docs/multi-distro/plan.md.
Use when
- Designing or validating
cloud-init user-data for first boot.
- Debugging why a cloud-init or autoinstall run failed.
- Bootstrapping a fresh Ubuntu server from declarative YAML rather than manual provisioning.
Do not use when
- The server is already live and you only need manual provisioning changes; use
linux-server-provisioning.
- The task is ordinary network or package troubleshooting outside cloud-init execution.
Required inputs
| Artefact | Source | Required? | If absent |
|---|
| User-data, autoinstall file, Kickstart file, or failure logs | Operator, image build, or affected host | required | Stop authoring or diagnosis and request the actual input. |
| Target distribution, release, image, and cloud/installer context | Operator or image metadata | required | Return a family-neutral outline; do not claim deployability. |
| Desired users, keys, packages, network state, and commands | Provisioning requirement | required for authoring | Produce a validation-only report if requirements are incomplete. |
Workflow
- Confirm whether the task is new authoring, validation, or post-failure debugging.
- Validate the YAML structure and cloud-init semantics before deployment.
- Follow the matching workflow below for user-data, autoinstall, or bootstrap scenarios.
- Inspect logs and rendered state after boot to prove the config applied as intended.
- Stop if parsing fails, the target family is unknown, required access would be lost, or a secret is embedded.
- Recover a failed test by correcting the source and rebuilding a disposable instance; do not treat rerunning once-per-instance modules as recovery.
Quality standards
- Keep configurations reproducible, explicit, and safe for unattended execution.
- Validate before rollout, especially for multi-server deployments.
- Treat logs and instance state as the source of truth when debugging.
Anti-patterns
- Shipping unvalidated YAML to multiple servers. Fix: run schema and syntax checks, then boot one disposable instance.
- Mixing first-boot responsibilities with ongoing state management. Fix: hand repeatable post-boot state to
linux-config-management.
- Assuming a failed command ran. Fix: inspect
cloud-init status --long, stage logs, and rendered configuration.
- Using Ubuntu package names or the
sudo group on a RHEL image. Fix: branch on the target family and use wheel plus family-correct packages.
- Retrying a poisoned instance indefinitely. Fix: correct the source input and rebuild because most cloud-init modules run once per instance.
Outputs
| Artefact | Consumer | Acceptance condition |
|---|
| Validated provisioning configuration | Image builder or cloud operator | Parser/schema checks pass and family-specific values match the target image. |
| Failure diagnosis | Incident owner | Names the failed stage, log evidence, root cause, and safe retry/rebuild path. |
| First-boot verification record | Provisioning handoff | Required users, SSH access, packages, services, and network state are observed on a test instance. |
References
Evidence Produced
| Artefact | Acceptance condition |
|---|
| Provisioning validation evidence | Contains parser output, test-instance cloud-init status --long, relevant log excerpts, and checks for every declared outcome. |
Capability contract
Read access to the source configuration is required. Execution on a disposable target is preferred. Editing, image publication, or production rebuilds require explicit authority; never place private keys or plaintext secrets in evidence.
Degraded mode
If no parser or disposable instance is available, perform a read-only structural review, label runtime checks not assessed, and return the exact commands the operator must run. An unbooted configuration is not validated.
Decision rules
| Choice | Action | Failure or risk avoided |
|---|
| Existing cloud image first boot | Use cloud-config/user-data | Installer-only directives being ignored. |
| Bare-metal or ISO install | Use Ubuntu autoinstall or RHEL Kickstart | Applying the wrong installer schema. |
| Ongoing configuration drift | Hand off to linux-config-management | Re-running once-per-instance modules as a configuration manager. |
Worked example
For a Rocky Linux image that must create an administrator and install Nginx, select cloud-config, use the wheel group and RHEL package names, validate the YAML, boot one disposable VM, then record the created user, key-only login, package version, and service state before scaling out.
This skill is self-contained. Every command below is a standard tool
present on both families (cloud-init, journalctl, yamllint) — see the
Distro support matrix above for what is portable versus family-specific.
The sk-*
scripts in the Optional fast path section are convenience wrappers —
never required.
This skill owns first-boot provisioning from YAML — cloud-init
user-data on cloud images (portable across both families), plus the
OS-install flow on the installer: Ubuntu's autoinstall and the RHEL
family's Kickstart (see
references/kickstart-reference.md).
It is the mechanism that takes a blank cloud image and turns it into a
server ready for linux-server-provisioning to finish.
It does not own:
- Interactive post-boot setup —
linux-server-provisioning.
- Cloud provider APIs (creating the VM in the first place) — out of
scope.
- Ongoing configuration management —
linux-config-management.
Informed by the Canonical Ubuntu Server Guide (cloud-init, autoinstall
chapters).
When to use
- Writing a
user-data YAML for a cloud image.
- Writing an Ubuntu
autoinstall config for a new installer ISO.
- Validating user-data before feeding it to a cloud provider.
- Debugging why a first-boot didn't install packages or create users.
- Extracting errors from
/var/log/cloud-init*.log on a provisioned host.
When NOT to use
- Day-2 config changes — use
linux-config-management (Ansible).
- Manual post-boot steps — run the relevant
linux-* skill directly.
Standing rules
- Validate every user-data file before using it. A broken
user-data silently ignores modules — you end up with an
under-configured server.
- Never put secrets in plain-text user-data. cloud-init caches it
under
/var/lib/cloud/ where it can be read later. Use vaulted
values or post-boot pulls from a secret store.
runcmd is last resort. Prefer first-class modules (users,
packages, write_files, ssh_authorized_keys) where possible.
They log cleanly and are idempotent-friendly.
- The very last
runcmd step in every production server user-data
should install linux-skills. Templates live in the references.
- Debug with the logs.
/var/log/cloud-init.log has the module
trace; /var/log/cloud-init-output.log has stdout/stderr of
runcmd.
- Autoinstall is a different schema than runtime user-data. Don't
cross them over. Autoinstall's cloud-init runs in a restricted
installer environment.
Quick reference — manual commands
Validate a user-data file
cloud-init schema --config-file user-data.yaml
cloud-init schema --config-file user-data.yaml --strict
yamllint user-data.yaml
Inspect cloud-init state on a running server
cloud-init status --long
cloud-init analyze show
cloud-init analyze blame
cloud-init analyze dump
cloud-init query --format '{{ ds.platform }} / {{ ds.region }}'
cloud-init query --all
Debug a failed run
sudo less /var/log/cloud-init.log
sudo less /var/log/cloud-init-output.log
sudo grep -iE "error|fail|traceback" /var/log/cloud-init.log
sudo grep "Running module" /var/log/cloud-init.log
sudo cloud-init clean --logs --seeds
sudo reboot
Autoinstall debugging (during install)
sudo less /var/log/installer/cloud-init.log
sudo less /var/log/installer/curtin-install.log
sudo less /var/log/installer/subiquity-server-debug.log
sudo journalctl -u cloud-init -u cloud-config -u cloud-final
Full user-data reference (every common module with 5 worked examples,
module ordering, idempotency, secrets note, datasource detection) — see
references/user-data-reference.md.
Full autoinstall reference (schema, storage layouts, LVM, ZFS,
autoinstall ISO build, serving over HTTP for PXE, 3 complete autoinstall
examples) — see
references/autoinstall-reference.md.
Full debugging guide (log layout, status decoding, re-run workflow) —
see references/debugging.md.
Typical workflows
Workflow: "Validate this user-data before I deploy 10 servers with it"
yamllint user-data.yaml
cloud-init schema --config-file user-data.yaml --strict
grep -E '^[a-z_]+:' user-data.yaml
grep -A20 '^runcmd:' user-data.yaml
lxc launch ubuntu:24.04 test --config=user.user-data="$(cat user-data.yaml)"
lxc exec test -- cloud-init status --wait
lxc exec test -- cloud-init status --long
lxc delete test --force
Workflow: "Why didn't my first-boot install nginx?"
sudo cloud-init status --long
sudo grep -A2 "packages" /var/log/cloud-init.log
sudo grep -iE "error|fail" /var/log/cloud-init-output.log | head -20
Workflow: "Bootstrap linux-skills via cloud-init"
Put this as the final runcmd block in your production user-data:
users:
- name: administrator
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAA... admin@example
packages:
- git
- curl
- unattended-upgrades
runcmd:
- sudo -u administrator bash -lc 'git clone https://github.com/<org>/linux-skills.git ~/.claude/skills'
- sudo bash /home/administrator/.claude/skills/scripts/setup-claude-code.sh
Full templates for a web server, Docker host, LXD guest, and database
server in references/user-data-reference.md.
Workflow: "Build an autoinstall ISO"
mkdir /tmp/autoinstall
cat > /tmp/autoinstall/user-data <<'EOF'
autoinstall:
version: 1
identity:
hostname: web01
username: administrator
password: '$6$...'
ssh:
install-server: true
authorized-keys:
- ssh-ed25519 AAAA...
EOF
touch /tmp/autoinstall/meta-data
cloud-init schema --config-file /tmp/autoinstall/user-data
cd /tmp/autoinstall && python3 -m http.server 3003
Troubleshooting / gotchas
- Indentation errors pass YAML parse but break cloud-init.
yamllint
doesn't enforce cloud-init semantics. Use
cloud-init schema --config-file as the real validator.
runcmd with a relative path fails silently. Always use absolute
paths: /usr/bin/apt-get, not apt-get. cloud-init's PATH is
minimal at runcmd time.
- Modules run once per instance-id. Re-running a playbook requires
sudo cloud-init clean (deletes state) + reboot. Without that,
cloud-init thinks it's already done.
- Long
package_upgrade: true on a slow mirror times out. The
install appears to hang, then continue without the upgraded packages.
Use package_upgrade: false for user-data where speed matters; run
unattended-upgrades after boot instead.
- Autoinstall storage config is unforgiving. A typo in the
storage
section produces an install that hangs at partitioning. Validate with
cloud-init schema and test in a VM before shipping the ISO.
write_files default encoding is text, not base64. For binary
files set encoding: b64 explicitly.
users: module replaces the default user completely unless you
include - default as the first entry.
References
Optional fast path (when sk-* scripts are installed)
Running sudo install-skills-bin linux-cloud-init installs:
| Task | Fast-path script |
|---|
| Validate user-data YAML + dry render of modules | sudo sk-cloud-init-validate --file <path> |
| Extract errors from cloud-init logs with module timeline | sudo sk-cloud-init-debug |
These are optional wrappers around cloud-init schema, cloud-init status, and cloud-init analyze.
Scripts
This skill installs the following scripts to /usr/local/bin/. To install:
sudo install-skills-bin linux-cloud-init
| Script | Source | Core? | Purpose |
|---|
| sk-cloud-init-validate | scripts/sk-cloud-init-validate.sh | no | Validate cloud-init user-data YAML against the schema and render a dry summary of modules. |
| sk-cloud-init-debug | scripts/sk-cloud-init-debug.sh | no | Extract errors from cloud-init logs, classify by module, show runcmd exit codes and boot timeline. |