Ubuntu cloud init
Claude Code plugin marketplace — 58 installable reference skills across vLLM/SGLang inference, Kubernetes & Harvester, GPU host bring-up, observability, security, and agent workflows.
npx -y skills add air-gapped/skills --skill ubuntu-cloud-initAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Author, validate, and debug cloud-init configuration for Ubuntu Server LTS 24.04 and 26.04, focused on on-premise and air-gapped hosts via the NoCloud datasource — `#cloud-config` user-data, the cloud-config modules (users, ssh, write_files, runcmd, apt with local mirrors, ca_certs for internal CAs, ntp, disk_setup), NoCloud seeding (seed dir, `cidata` ISO, `ds=nocloud;s=...` kernel cmdline, SMBIOS serial), boot stages, and pinning `datasource_list`. Public-cloud datasources (EC2/Azure/GCE…) are pointers only. For the `network:` block use the ubuntu-netplan skill; for the installer schema use the ubuntu-autoinstall skill.
SKILL.md
12.1 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it
ubuntu-cloud-init
cloud-init runs early in boot, reads configuration from a datasource, and applies it through modules across four boot stages. This skill is authoring-led (produce correct cloud-config from a description) with a strong validation/debug path, aimed at on-prem / air-gapped Ubuntu Server LTS using the NoCloud datasource.
Mapping: 24.04 LTS ≈ cloud-init 24.x; 26.04 LTS ≈ 26.1.
Authoring workflow
- Identify the goal & datasource. On-prem/air-gapped → NoCloud (seed dir,
cidataISO/USB, HTTP seed, or SMBIOS serial). Pindatasource_list: [NoCloud]to stop cloud-init probing cloud metadata services and timing out. - Choose the user-data format — almost always
#cloud-config. Use MIME multipart only to combine cloud-config with scripts. (Formats table below.) - Write the
#cloud-configusing the modules needed (see Quick reference). - Validate:
cloud-init schema -c user-data --annotate. - Seed it (NoCloud) and set a unique
instance-idinmeta-data. - Test on a throwaway boot, then
cloud-init clean --logsto re-run as if first boot.
user-data formats
First line determines the format:
| Header (line 1) | Format |
|---|---|
#cloud-config | YAML config processed by modules (the usual choice). |
#!/bin/sh (shebang) | A script, run once per instance in the Final stage. |
#cloud-boothook | Runs very early, every boot (guard with cloud-init-per). |
#include | List of URLs, each fetched as user-data. |
Content-Type: multipart/mixed | MIME — combine cloud-config + scripts. Build with cloud-init devel make-mime. |
## template: jinja | Jinja template (line 1); real header on line 2. Variables = instance-data keys, e.g. {{ v1.instance_id }}. |
Any of these may be gzipped.
Quick reference — the modules that matter on-prem
users / ssh (created in the Network stage):
#cloud-config
ssh_pwauth: false
users:
- default # KEEP this first to retain the distro user + cloud keys
- name: deploy
groups: [sudo]
sudo: "ALL=(ALL) NOPASSWD:ALL"
lock_passwd: true
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAA... deploy@site
passwd (a hash) applies only to new users; hashed_passwd applies even to
existing ones. Generate a hash with mkpasswd --method=SHA-512 --rounds=4096.
write_files:
write_files:
- path: /etc/myapp/app.conf
owner: root:root
permissions: '0640'
content: |
backend = db.internal:5432
- encoding: b64 # also gz, gz+b64
content: aGVsbG8K
path: /etc/motd.d/banner
defer: true # write in Final stage (after users/packages)
runcmd vs bootcmd: runcmd runs once per instance in the Config stage (later);
bootcmd runs early on every boot (guard once-only work with
cloud-init-per once <name> ...).
runcmd:
- [systemctl, enable, --now, myapp]
apt — local mirror + internal repo (the air-gapped essential):
apt:
preserve_sources_list: false
primary:
- arches: [default]
uri: http://mirror.internal/ubuntu
security:
- arches: [default]
uri: http://mirror.internal/ubuntu
sources:
internal-app.list:
source: "deb [signed-by=$KEY_FILE] http://mirror.internal/app stable main"
key: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
...embed the key inline; do NOT rely on a keyserver air-gapped...
-----END PGP PUBLIC KEY BLOCK-----
conf: |
Acquire::http::Proxy "http://apt-cache.internal:3142";
ca_certs — trust an internal CA (runs in the Network stage, before apt, so HTTPS to the internal mirror works):
ca_certs:
trusted:
- |
-----BEGIN CERTIFICATE-----
<internal root CA>
-----END CERTIFICATE-----
ntp — point at local time source:
ntp:
enabled: true
servers: [ntp.internal]
pools: []
Other on-prem-relevant modules: packages/package_update/package_upgrade
(avoid package_upgrade without a full mirror), disk_setup+fs_setup+mounts,
growpart/resize_rootfs, timezone/locale/keyboard, set_passwords/chpasswd,
seed_random, power_state, hostname (hostname/fqdn/manage_etc_hosts). Avoid
phone_home air-gapped — it only calls out to a URL and retries on failure. Full
per-module detail (keys, snippets, stage, frequency) in references/modules.md.
NoCloud seeding (the air-gapped core)
cloud-init finds NoCloud config from (in precedence order) SMBIOS serial → kernel cmdline → seed dirs → labeled block device. The common methods:
- Seed directory: drop
meta-data+user-data(and optionallyvendor-data,network-config) into/var/lib/cloud/seed/nocloud/. cidataISO/USB: avfat/iso9660filesystem labeledCIDATA(case-insensitive) containing those files in its root.genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data network-config(orcloud-localds seed.iso user-data meta-data).- HTTP seed: kernel cmdline
ds=nocloud;s=http://10.0.0.1:8000/(the scheme decides local vs network; trailing slash required — files are appended as<uri>/user-dataetc.). - SMBIOS serial (QEMU/libvirt):
-smbios type=1,serial=ds=nocloud;s=http://10.0.0.1:8000/.
Required files: meta-data and user-data. Optional: vendor-data,
network-config. Since cloud-init 24.3, an HTTP/seed source that omits
network-config triggers a boot retry/timeout — ship an empty network-config
for back-compat, or a real netplan-v2 one.
instance-id re-run rule: cloud-init only re-applies user-data when the
instance-id in meta-data changes (or after cloud-init clean). Bump it
whenever the config changes.
Full seeding detail (cmdline grammar + aliases, DMI variable expansion, FTP, GRUB
escaping, seedfrom, dsname deprecations) is in references/nocloud-and-airgapped.md.
Network config (hand-off to netplan)
cloud-init network-config has v1 (its own schema) and v2 (which IS netplan
format). On Ubuntu the renderer is netplan; cloud-init writes
/etc/netplan/50-cloud-init.yaml. user-data cannot set network config —
networking comes from the datasource, system config, or kernel cmdline.
To author the network block, or to hand network control back to netplan entirely
(network: {config: disabled} in /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg),
use the ubuntu-netplan skill.
Validation & debugging (fast path)
cloud-init schema -c user-data --annotate # validate a file, errors inline
sudo cloud-init schema --system --annotate # validate the live system's user-data
cloud-init status --long --wait # 0=ok, 1=error, 2=recoverable error
cloud-init query --all # inspect instance-data
sudo cloud-init clean --logs # wipe state → next boot is "first boot"
Logs: /var/log/cloud-init.log, /var/log/cloud-init-output.log,
/run/cloud-init/ (incl. ds-identify.log, result.json, instance-data.json).
Config: /etc/cloud/cloud.cfg + /etc/cloud/cloud.cfg.d/*.cfg. Full CLI, the boot
stages, and re-run/golden-image notes in references/cli-and-debugging.md.
Air-gapped checklist
- Pin
datasource_list: [NoCloud](or[NoCloud, None]) in acloud.cfg.ddrop-in — stops cloud probing/timeouts. - Local apt mirror via the
aptmodule; embed repo signing keys inline. - Internal CA via
ca_certs.trusted. - Local NTP via
ntp.servers. - Omit
phone_home; don'tpackage_upgradewithout a full mirror; avoidsnapunless a local store / pre-acked assertions are available. - Use
vendor-datafor site-wide defaults (mirror, CA, NTP, base users); per-hostuser-dataoverrides it.
Upstream breaking changes worth knowing (doc/rtd/reference/breaking_changes.rst)
Verified against main on 2026-07-21. Ubuntu vendors patch some of these out —
the doc says so itself — so confirm against the image before assuming.
25.1.4 — strict datasource identity before network. The one most likely to
bite an on-prem/ARM fleet. ds-identify now requires strict identification
from DMI platform data, the kernel command line, or an explicit
datasource_list: in /etc/cloud/cloud.cfg.d. Previously, a platform without
clear identifying data fell into a late discovery mode that brought networking
up and reached out to well-known link-local IPs to fetch config — the hardening
exists to stop a bad actor on the local network answering those requests.
- Most affected: Ec2, OpenStack and AltCloud on non-x86, where the kernel may not expose DMI data.
- Failure mode is silent-ish: if no datasource is identified, cloud-init stays disabled and performs no configuration at all during boot — the machine simply comes up unconfigured.
- Mitigations: launch with
openstack server create … --config-drive true, or pindatasource_list:explicitly. This is a second, stronger reason for thedatasource_list: [NoCloud]pin already recommended in the air-gapped checklist — it is no longer only about avoiding probe timeouts.
Also recorded, lower operational impact for this skill's use cases:
| Version | Change |
|---|---|
| 26.1 | OpenStack bond names are no longer hard-coded to bond0/bond1… — they now take whatever network_data.json provides |
| 25.3 | Systemd socket protocol changed for compatibility with openbsd-netcat alternatives (e.g. nmap's ncat -U) — downstreams shipping a custom ExecStart= must update. Build backend moved setuptools/distutils → meson (PEP-0632); packagers should diff the generated package |
| 25.1 | /usr merge — packaging installs nothing to /lib any more, everything to /usr/lib. Affects non-systemd / older / non-Linux distros |
| 24.4 | cloud-final.service ordering standardized — changed the systemd boot order on some distributions |
| 24.3 | cloud-init.service → cloud-init-network.service (already covered above) |
| 24.1 | ds-identify no longer auto-appends None to a single-entry datasource_list (already covered above) |
Boundaries (sibling skills)
network:→ ubuntu-netplan skill (v2 is netplan format).- Ubuntu Server install automation → ubuntu-autoinstall skill. The installer
is itself driven by a NoCloud
user-datacarrying a top-levelautoinstall:key; cloud-init ignoresautoinstalland passes it to Subiquity. Inside that document, the nesteduser-data:is cloud-config for the installed system (first boot).
Reference files
references/nocloud-and-airgapped.md— every NoCloud seeding method, cmdline grammar, SMBIOS/DMI,instance-id,datasource_listpinning, DataSourceNone, vendor-data, deprecations.references/modules.md— full cloud-config module reference (on-prem modules in depth; the rest listed by stage). Has a TOC.references/cli-and-debugging.md— CLI, boot stages & module ordering, re-run / golden-image, version notes (24.x vs 26.1).references/examples.md— minimal and realistic air-gapped configs with matchingmeta-data.
What ships with it: 7 files
34.3 KB alongside SKILL.md
references/
- cli-and-debugging.md5.3 KB
- examples.md3.6 KB
- improvement-backlog.md5.7 KB
- modules.md7.9 KB
- nocloud-and-airgapped.md5.5 KB
- sources.md4.6 KB
- trigger-evals.json1.6 KB