Is there a way to configure lxd containers with cloud config at provision time?

So there are multiple ways you can do so, either directly for a container:

lxc init ubuntu: CONTAINER
lxc config set CONTAINER user.user-data - < cloud-init-config.yml
lxc start CONTAINER

Or even shorter:

lxc launch ubuntu: CONTAINER --config=user.user-data="$(cat cloud-init-config.yml)"

Or through a profile:

lxc profile create dev
lxc profile set dev user.user-data - < cloud-init-config.yml
lxc launch ubuntu: CONTAINER -p default -p dev

I had a slightly more specific question than the OP but it took me a while to work out what I was doing wrong. I thought I would post it here to help anyone else getting similarly stumped.

I wanted static networking settings for an LXC/LXD Ubuntu 16.04 container hosted on Ubuntu 16.04. I started by trying what Stéphane wrote but it wasn't working. All I ended up with was the default DHCP-attempting container with an IPv6 link local, as there is no DHCP being served in my configuration.

My initial YAML looked (something) like the following (taken from the cloud-init docs).

network:
  version: 1
  config:
    - type: physical
      name: eth0
      subnets:
        - type: static
          address: 192.168.23.14/27
          gateway: 192.168.23.1
          dns_nameservers:
            - 192.168.23.2
            - 8.8.8.8
          dns_search:
            - exemplary.maas

And I was loading this into user.user-data as described above.

lxc config set CONTAINER user.user-data - < CONTAINER.cloud-init-config.yml

It wasn't until I found Stéphane's documentation in the LXC/LXD source that I realised I needed to load that value into user.network-config.

So my final YAML looked (something) like this.

version: 1
config:
  - type: physical
    name: eth0
    subnets:
      - type: static
        address: 192.168.23.14/27
        gateway: 192.168.23.1
        dns_nameservers:
          - 192.168.23.2
          - 8.8.8.8
        dns_search:
          - exemplary.maas

Then I loaded this into user.network-config instead.

lxc config set CONTAINER user.network-config - < CONTAINER.network-config.yaml

It seems like I will need to keep two different files per container: one for network settings to load to user.network-config; and one for other config to load into user.user-data unless I can find a way of using a single file for everything.


Another problem I found which wasn't obvious to me at all was trying to configure non-network components automatically.

lxc config set CONTAINER user.user-data - < CONTAINER.user-data.yaml

The following YAML applied with the command above (despite looking correct using lxc config show CONTAINER) did not create anything inside my container.

write_files:
  - content: |
    # My new /etc/foo.bar file

    Foo
    Bar

    path: /etc/foo.bar

The clue buried away in User Data Input Formats, item 5: Cloud Config Data reads:

begins with "#cloud-config" or "Content-Type: text/cloud-config" This content is "cloud-config" data. See the examples for a commented example of supported config formats.

I don't believe this documentation is very clear. I could not get anything to work using the "Content-Type: text/cloud-config" form but I did find if you put #cloud-config on the first line, the YAML is parsed. I can only assume something isn't quite right, either my understanding, or someone's programming. It makes no sense to me that YAML you've explicitly loaded as the value of the key user.user-data should be used as anything other than cloud configuration data. Why else would anyone do that if it wasn't meant to be cloud configuration, and therefore why would a comment (which doesn't even use the normal shebang syntax) be required?

So, nonsense aside, the syntax which worked for user.user-data is:

#cloud-config

write_files:
  - content: |
      # My new /etc/foo.bar file

      Foo
      Bar

    path: /etc/foo.bar

One liner I went with today, this one sets it in the default profile for new containers:

echo -e "#cloud-config\nssh_authorized_keys:\n - $(cat ~/.ssh/id_rsa.pub)" | lxc profile set default user.user-data -

This one sets it in an existing container, but beware it will not work on containers that already booted as the SSH key stuff only gets done on first boot:

echo -e "#cloud-config\nssh_authorized_keys:\n - $(cat ~/.ssh/id_rsa.pub)" | lxc config set CONTAINER_NAME user.user-data -