How to use cloud-init
▶ Watch on YouTube (opens in a new tab)
cloud-init (opens in a new tab) is a tool for automatically initializing and customizing an instance of a Linux distribution.
By adding cloud-init configuration to your instance, you can instruct cloud-init to execute specific actions at the first start of an instance. Possible actions include, for example:
-
Updating and installing packages
-
Applying certain configurations
-
Adding users
-
Enabling services
-
Running commands or scripts
-
Automatically growing the file system of a VM to the size (quota) of the disk
See the Cloud-init documentation (opens in a new tab) for detailed information.
Note
The cloud-init actions are run only once on the first start of the instance. Rebooting the instance does not re-trigger the actions.
cloud-init support in images
To use cloud-init, you must base your instance on an image that has cloud-init installed:
-
All images from the
ubuntuandubuntu-dailyimage servers havecloud-initsupport. However, images for Ubuntu releases prior to 20.04 LTS require special handling to integrate properly withcloud-init, so thatlxc execworks correctly with virtual machines that use those images. Refer to VMcloud-init. -
Images from the
imagesremote (opens in a new tab) havecloud-init-enabled variants, which are usually bigger in size than the default variant. The cloud variants use the/cloudsuffix, for example,images:alpine/edge/cloud.
Configuration options
LXD supports two different sets of configuration options for configuring cloud-init: cloud-init.* and user.*. Which of these sets you must use depends on the cloud-init support in the image that you use. As a rule of thumb, newer images support the cloud-init.* configuration options, while older images support user.*. However, there might be exceptions to that rule.
The following configuration options are supported:
-
cloud-init.vendor-dataoruser.vendor-data(see Vendor-data (opens in a new tab)) -
cloud-init.user-dataoruser.user-data(see User-data formats (opens in a new tab)) -
cloud-init.network-configoruser.network-config(see Network configuration (opens in a new tab))
For more information about the configuration options, see the cloud-init instance options, and the documentation for the LXD data source (opens in a new tab) in the cloud-init documentation.
Note
Ubuntu 20.04 and earlier have recent versions of the cloud-init package but support for the modern cloud-init.* configuration options is disabled in those series. As such, when using such old instances, remember to use the user.* configuration options instead.
Vendor data and user data
Both vendor-data and user-data are used to provide cloud configuration data (opens in a new tab) to cloud-init.
The main idea is that vendor-data is used for the general default configuration, while user-data is used for instance-specific configuration. This means that you should specify vendor-data in a profile and user-data in the instance configuration. LXD does not enforce this method, but allows using both vendor-data and user-data in profiles and in the instance configuration.
If both vendor-data and user-data are supplied for an instance, cloud-init merges the two configurations. However, if you use the same keys in both configurations, merging might not be possible. In this case, configure how cloud-init should merge the provided data. See Merging user-data sections (opens in a new tab) for instructions.
How to configure cloud-init
To configure cloud-init for an instance, add the corresponding configuration options to a profile that the instance uses or directly to the instance configuration.
When configuring cloud-init directly for an instance, keep in mind that cloud-init runs only on instance start. This means any changes to cloud-init configuration only take effect after the next instance start. To ensure cloud-init configurations are applied on every boot, LXD changes the instance ID whenever relevant cloud-init configuration keys are modified. This triggers cloud-init to fetch and apply the updated data from LXD as if it were the instance’s first boot. For more information, see the cloud-init docs regarding First boot determination (opens in a new tab).
To add your configuration:
CLIAPIUI
Write the configuration to a file and pass that file to the lxc config command. For example, create cloud-init.yml with the following content:
#cloud-config
package_upgrade: true
packages:
- package1
- package2Then run the following command:
lxc config set <instance_name> cloud-init.user-data - < cloud-init.yml
Provide the cloud-init configuration as a string with escaped newline characters.
For example:
lxc query --request PATCH /1.0/instances/<instance_name> --data '{
"config": {
"cloud-init.user-data": "#cloud-config\\npackage_upgrade: true\\npackages:\\n - package1\\n - package2"
}
}'Alternatively, to avoid mistakes, write the configuration to a file and include that in your request. For example, create cloud-init.txt with the following content:
#cloud-config
package_upgrade: true
packages:
- package1
- package2Then send the following request:
lxc query --request PATCH /1.0/instances/<instance_name> --data '{
"config": {
"cloud-init.user-data": "'"$(awk -v ORS='\\\\n' '1' cloud-init.txt)"'"
}
}'Go to the Configuration tab of the instance detail page and select Advanced > Cloud init. Then click Edit instance and override the configuration for one or more of the cloud-init configuration options.
YAML format for cloud-init configuration
The cloud-init options require YAML’s literal style format (opens in a new tab). You use a pipe symbol (|) to indicate that all indented text after the pipe should be passed to cloud-init as a single string, with new lines and indentation preserved.
The vendor-data and user-data options usually start with #cloud-config. But cloud-init has an array of configuration formats (opens in a new tab) available.
For example:
config:
cloud-init.user-data: |
#cloud-config
package_upgrade: true
packages:
- package1
- package2config:
cloud-init.user-data: |
#!/usr/bin/bash
echo hello | tee -a /tmp/example.txtTip
See How to validate user data (opens in a new tab) for information on how to check whether the syntax is correct.
How to check the cloud-init status
cloud-init runs automatically on the first start of an instance. Depending on the configured actions, it might take a while until it finishes.
To check the cloud-init status, log on to the instance and enter the following command:
cloud-init status
If the result is status: running, cloud-init is still working. If the result is status: done, it has finished.
Alternatively, use the --wait flag to be notified only when cloud-init is finished:
root@instance:~# cloud-init status --wait
.....................................``status: done
How to specify user or vendor data
The user-data and vendor-data configuration can be used to, for example, upgrade or install packages, add users, or run commands.
The provided values must have a first line that indicates what type of user data format (opens in a new tab) is being passed to cloud-init. For activities like upgrading packages or setting up a user, #cloud-config is the data format to use.
The configuration data is stored in the following files in the instance’s root file system:
-
/var/lib/cloud/instance/cloud-config.txt -
/var/lib/cloud/instance/user-data.txt
Examples
See the following sections for the user data (or vendor data) configuration for different example use cases.
You can find more advanced examples (opens in a new tab) in the cloud-init documentation.
Upgrade packages
To trigger a package upgrade from the repositories for the instance right after the instance is created, use the package_upgrade key:
config:
cloud-init.user-data: |
#cloud-config
package_upgrade: trueInstall packages
To install specific packages when the instance is set up, use the packages key and specify the package names as a list:
config:
cloud-init.user-data: |
#cloud-config
packages:
- git
- openssh-serverSet the time zone
To set the time zone for the instance on instance creation, use the timezone key:
config:
cloud-init.user-data: |
#cloud-config
timezone: Europe/RomeRun commands
To run a command (such as writing a marker file), use the runcmd key and specify the commands as a list:
config:
cloud-init.user-data: |
#cloud-config
runcmd:
- [touch, /run/cloud.init.ran]Add a user account
To add a user account, use the users key. See the Including users and groups (opens in a new tab) example in the cloud-init documentation for details about default users and which keys are supported.
config:
cloud-init.user-data: |
#cloud-config
users:
- name: documentation_exampleHow to specify network configuration data
By default, cloud-init configures a DHCP client on an instance’s eth0 interface. You can define your own network configuration using the network-config option to override the default configuration (this is due to how the template is structured).
cloud-init then renders the relevant network configuration on the system using either ifupdown or netplan, depending on the Ubuntu release.
The configuration data is stored in the following files in the instance’s root file system:
-
/var/lib/cloud/seed/nocloud-net/network-config -
/etc/network/interfaces.d/50-cloud-init.cfg(if usingifupdown) -
/etc/netplan/50-cloud-init.yaml(if usingnetplan)
Example
To configure a specific network interface with a static IPv4 address and also use a custom name server, use the following configuration:
config:
cloud-init.network-config: |
version: 2
ethernets:
eth1:
addresses:
- 10.10.101.20/24
gateway4: 10.10.101.1
nameservers:
addresses:
- 10.10.10.254How to inject SSH keys into instances
To inject SSH keys into LXD instances for an arbitrary user, use the configuration key cloud-init.ssh-keys.<keyName>.
Use the format <user>:<key> for its value, where <user> is a Linux username and <key> can be either a pure SSH public key or an import ID for a key hosted elsewhere. For example, root:gh:githubUser and myUser:ssh-keyAlg publicKeyHash are valid values. To prevent a particular SSH key from being inherited from a profile by an instance, edit the instance configuration by setting the cloud-init.ssh-keys.<keyName> key that references the target SSH key to none, and the key will not be injected.
The contents of the cloud-init.ssh-keys.<keyName> keys are merged into both cloud-init.vendor-data and cloud-init.user-data before being passed to the guest, following the cloud-config specification. (See the cloud-init documentation (opens in a new tab) for details.) Therefore, keys defined via cloud-init.ssh-keys.<keyName> cannot be applied if LXD cannot parse the existing cloud-init.vendor-data and cloud-init.user-data for that instance. This might occur if those keys are not in YAML format or contain invalid YAML. Other configuration formats are not yet supported.
You can define SSH keys via cloud-init.vendor-data or cloud-init.user-data directly. Keys defined using cloud-init.ssh-keys.<keyName> do not conflict with those defined in either of those settings. For details on defining SSH keys with cloud-config, see the cloud-init documentation for SSH configuration (opens in a new tab). Changing a cloud-init.* key does not remove previously applied keys.
Since cloud-init only runs on instance start, updates to cloud-init.* keys on a running instance only take effect after restart.
Examples
The following command injects someuser’s key from Launchpad into the newly created container:
lxc launch ubuntu:24.04 container -c cloud-init.ssh-keys.mykey=root:lp:someuser
The example profile configuration below defines a key to be injected on an instance. The injected key enables the owner of the private key to SSH into the instance as a user named user:
config:
cloud-init.vendor-data: |
users:
- name: user
ssh_authorized_keys: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJFDWcYmMrCZdk9JI29bAiHKD90oEUr8tqK5VvoO8Vcj