User Data for First-Boot Setup

User data is up to 16 KB of text passed at launch. On first boot cloud-init runs it as root, either as a #! script or, when it starts with #cloud-config, as declarative YAML:

lamp-user-data.yaml: turning a fresh Ubuntu instance into a LAMP serverYAML
#cloud-config
package_update: true
packages:
  - apache2
  - libapache2-mod-php
  - php-mysql
  - php-mbstring
  - php-xml
  - mysql-server
swap:
  filename: /swapfile
  size: 1G
write_files:
  - path: /var/www/html/index.php
    defer: true
    owner: www-data:www-data
    permissions: '0644'
    content: |
      <?php
      printf("Hello from %s, PHP %s, %s\n",
          gethostname(), PHP_VERSION, php_sapi_name());
runcmd:
  - rm -f /var/www/html/index.html
  - systemctl enable --now apache2 mysql

The swap file matters on 1 GiB with MySQL 524 . defer: true waits until the packages exist, so the www-data owner is valid, and runcmd runs last. SSH comes up before setup ends, so wait for it:

Waiting for cloud-init and reading its verdictShell
cloud-init status --wait; cloud-init status --long
Output
...status: done
status: done
extended_status: done
...
errors: []

The package step took 56.3 seconds (cloud-init analyze show), and the whole setup finished 73.88 seconds after boot. On failure, read /var/log/cloud-init-output.log. User data runs once per instance, and any process on the instance can read it from the metadata service, so never put secrets in it. Add package_upgrade: true for production: 17 updates were already waiting.