How to use cloud-init

cloud-init 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 of the disk

See the Cloud-init documentation 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 ubuntu and ubuntu-daily image servers have cloud-init support. However, images for Ubuntu releases prior to 20.04 require special handling to integrate properly with cloud-init, so that lxc exec works correctly with virtual machines that use those images. Refer to VM cloud-init.

  • Images from the images remote have cloud-init-enabled variants, which are usually bigger in size than the default variant. The cloud variants use the /cloud suffix, 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:

For more information about the configuration options, see the cloud-init instance options, and the documentation for the LXD data source in the cloud-init documentation.

Vendor data and user data

Both vendor-data and user-data are used to provide cloud configuration data 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 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 the first start of the instance. That means that you must configure cloud-init before you start the instance. If you are using the CLI client, create the instance with lxc init instead of lxc launch, and then start it after completing the configuration.

YAML format for cloud-init configuration

The cloud-init options require YAML’s literal style format. 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.

For example:

config:
  cloud-init.user-data: |
    #cloud-config
    package_upgrade: true
    packages:
      - package1
      - package2

Tip

See How to validate user data for information on how to check whether the syntax is correct.

Configure cloud-init through the API

If you are using the API to configure your instance, 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
  - package2

Then 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)"'"
  }
}'

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 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 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: true

Install 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-server

Set 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/Rome

Run 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 user key. See the Including users and groups example in the cloud-init documentation for details about default users and which keys are supported.

config:
  cloud-init.user-data: |
    #cloud-config
    user:
      - name: documentation_example

How 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 using ifupdown)

  • /etc/netplan/50-cloud-init.yaml (if using netplan)

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: 1
    config:
      - type: physical
        name: eth1
        subnets:
          - type: static
            ipv4: true
            address: 10.10.101.20
            netmask: 255.255.255.0
            gateway: 10.10.101.1
            control: auto
      - type: nameserver
        address: 10.10.10.254