Skip to Content
DocsServerCloud-Init

cloud-init

cloud-init is the industry-standard tool for cloud instance initialization, automatically completing system configuration on first boot. Ubuntu 26.04 Server comes with cloud-init pre-installed and supports major cloud platforms such as AWS, Azure, GCP, and others.

How It Works

cloud-init runs through the following stages during instance startup:

  1. Generator - Detects whether the instance is running in a cloud environment
  2. Local - Obtains network configuration from local data sources
  3. Network - Fetches metadata and user-data
  4. Config - Executes configuration modules
  5. Final - Executes final modules (e.g., running scripts)
# Check cloud-init version cloud-init --version # Check cloud-init status cloud-init status # View detailed status cloud-init status --long

Data Sources

Metadata

Instance metadata provided by the cloud platform, including instance ID, IP address, hostname, etc.:

# Retrieve metadata on AWS EC2 (IMDSv2) TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/ # Retrieve metadata on GCP curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/ # Retrieve metadata on Azure curl -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"

User-data

User-defined initialization configuration that supports multiple formats.

user-data Configuration

Cloud-config Format (Most Common)

A YAML file starting with #cloud-config:

#cloud-config # Set hostname hostname: web-server-01 fqdn: web-server-01.example.com # Create users users: - name: deploy groups: sudo, docker shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL ssh_authorized_keys: - ssh-ed25519 AAAA... deploy@workstation - name: monitor groups: [] shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAA... monitor@management # Set timezone timezone: America/New_York # NTP configuration ntp: enabled: true servers: - pool.ntp.org - time.google.com # Locale locale: en_US.UTF-8 # Change APT mirror apt: primary: - arches: [default] uri: http://archive.ubuntu.com/ubuntu # Install packages packages: - nginx - docker.io - fail2ban - htop - curl - vim # Upgrade all packages package_update: true package_upgrade: true package_reboot_if_required: true # Write files write_files: - path: /etc/nginx/sites-available/default content: | server { listen 80; server_name _; root /var/www/html; index index.html; } owner: root:root permissions: '0644' - path: /etc/sysctl.d/99-custom.conf content: | net.ipv4.ip_forward = 1 net.core.somaxconn = 65535 owner: root:root permissions: '0644' # Run commands (executed during cloud-init config stage) runcmd: - sysctl -p /etc/sysctl.d/99-custom.conf - systemctl enable --now nginx - systemctl enable --now fail2ban - ufw allow ssh - ufw allow 80/tcp - ufw allow 443/tcp - ufw --force enable # Enable/disable SSH password login ssh_pwauth: false # SSH key configuration ssh_keys: ed25519_private: | -----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY----- ed25519_public: ssh-ed25519 AAAA... # Set passwords (hashed) chpasswd: expire: false users: - name: deploy type: RANDOM # Final message final_message: "Cloud-init complete! Took $UPTIME seconds." # Post-boot action power_state: mode: reboot message: "cloud-init configuration complete, rebooting..." timeout: 30 condition: true

Shell Script Format

Starting with #!/bin/bash:

#!/bin/bash set -euxo pipefail # Update the system apt update && apt upgrade -y # Install base tools apt install -y nginx docker.io # Configure the firewall ufw allow ssh ufw allow 80/tcp ufw --force enable # Custom script echo "Setup complete at $(date)" > /var/log/cloud-init-custom.log

Multi-Part Format (MIME Multi-Part)

Combine multiple formats:

# Use the cloud-init tool to create a multi-part message cloud-init devel make-mime \ --attach script.sh:text/x-shellscript \ --attach config.yaml:text/cloud-config \ > combined-userdata.txt

Common Use Cases

Initialize a Docker Environment

#cloud-config packages: - docker.io - docker-compose-v2 groups: - docker users: - name: deploy groups: sudo, docker shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL ssh_authorized_keys: - ssh-ed25519 AAAA... write_files: - path: /etc/docker/daemon.json content: | { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } runcmd: - systemctl enable --now docker - docker pull nginx:alpine

Initialize Disks

#cloud-config disk_setup: /dev/vdb: table_type: gpt layout: true overwrite: false fs_setup: - label: data filesystem: ext4 device: /dev/vdb1 overwrite: false mounts: - ["/dev/vdb1", "/data", "ext4", "defaults,noatime", "0", "2"] runcmd: - mkdir -p /data

Join a Cluster

#cloud-config runcmd: - curl -sfL https://get.k3s.io | K3S_URL=https://master:6443 K3S_TOKEN=mytoken sh -

Local Testing

Using Multipass

# Install Multipass sudo snap install multipass # Launch an instance with cloud-config multipass launch 26.04 --name test --cloud-init cloud-config.yaml # Enter the instance multipass shell test # View cloud-init logs multipass exec test -- cat /var/log/cloud-init-output.log

Using LXD

# Test cloud-init via LXD lxc launch ubuntu:26.04 test --config=user.user-data="$(cat cloud-config.yaml)" lxc exec test -- cloud-init status --wait lxc exec test -- cat /var/log/cloud-init-output.log

Validate Configuration Locally

# Validate cloud-config syntax cloud-init schema --config-file cloud-config.yaml # Use the devel subcommand for debugging cloud-init devel render /path/to/userdata.yaml

Logs and Debugging

# Main log file sudo cat /var/log/cloud-init.log # Output log sudo cat /var/log/cloud-init-output.log # View executed modules sudo cat /run/cloud-init/result.json # View user data sudo cat /var/lib/cloud/instance/user-data.txt # View instance data sudo cloud-init query --all # Query specific fields sudo cloud-init query instance_id sudo cloud-init query region sudo cloud-init query local_hostname

Re-run cloud-init

# Clear cloud-init state (for testing) sudo cloud-init clean # Clear and reboot to re-execute sudo cloud-init clean --reboot # Clear logs only sudo cloud-init clean --logs # Manually trigger specific stages sudo cloud-init init sudo cloud-init modules --mode config sudo cloud-init modules --mode final

Disable cloud-init

In non-cloud environments, you can disable cloud-init:

# Method 1: Create a disable marker file sudo touch /etc/cloud/cloud-init.disabled # Method 2: Via GRUB kernel parameter # Add the following to GRUB_CMDLINE_LINUX in /etc/default/grub: # cloud-init=disabled # Method 3: Uninstall sudo apt remove cloud-init -y sudo rm -rf /etc/cloud /var/lib/cloud
Last updated on