Docs / Getting Started / Getting Started with Cloud-Init User Data Scripts

Getting Started with Cloud-Init User Data Scripts

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 218 views · 4 min read

Cloud-init is the industry standard for automating the initial setup of cloud instances. When you deploy a new Breeze, you can provide a user data script that runs automatically on first boot — installing packages, creating users, configuring services, and deploying your application without any manual SSH sessions.

What Is Cloud-Init?

Cloud-init is a multi-distribution package that handles early initialization of a cloud instance. It runs during the first boot and can configure nearly every aspect of the system. Most Linux cloud images (Ubuntu, Debian, AlmaLinux, Rocky) come with cloud-init pre-installed.

Basic User Data Script

The simplest format is a bash script starting with #!:

#!/bin/bash
# This script runs as root on first boot

# Update the system
apt update && apt upgrade -y

# Install essential packages
apt install -y nginx mysql-server php8.3-fpm php8.3-mysql

# Enable and start services
systemctl enable --now nginx
systemctl enable --now mysql
systemctl enable --now php8.3-fpm

# Create a non-root user
useradd -m -s /bin/bash deploy
echo "deploy ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/deploy

# Set up SSH key for the new user
mkdir -p /home/deploy/.ssh
echo "ssh-ed25519 AAAA... your-key" > /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

Cloud-Config YAML Format

For more complex setups, cloud-init supports a YAML-based configuration format:

#cloud-config

# Set the hostname
hostname: web-prod-01
fqdn: web-prod-01.example.com

# Create users
users:
  - name: deploy
    groups: sudo
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... your-key

# Install packages
packages:
  - nginx
  - certbot
  - python3-certbot-nginx
  - fail2ban
  - ufw
  - htop
  - git

# Run commands on first boot
runcmd:
  - ufw allow ssh
  - ufw allow http
  - ufw allow https
  - ufw --force enable
  - systemctl enable --now fail2ban
  - systemctl enable --now nginx

# Write configuration files
write_files:
  - path: /etc/nginx/sites-available/myapp.conf
    content: |
      server {
          listen 80;
          server_name example.com;
          root /var/www/myapp/public;
          index index.php index.html;

          location / {
              try_files $uri $uri/ /index.php?$query_string;
          }

          location ~ .php$ {
              fastcgi_pass unix:/run/php/php8.3-fpm.sock;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
              include fastcgi_params;
          }
      }
    owner: root:root
    permissions: "0644"

# Set timezone
timezone: America/New_York

# Configure automatic security updates
package_update: true
package_upgrade: true

Advanced: Full LEMP Stack Deployment

#!/bin/bash
set -euo pipefail

# Variables
DB_NAME="myapp"
DB_USER="myapp"
DB_PASS=$(openssl rand -base64 24)
DOMAIN="example.com"

# System setup
apt update && apt upgrade -y
apt install -y nginx mysql-server php8.3-fpm php8.3-mysql 
  php8.3-curl php8.3-xml php8.3-mbstring php8.3-zip 
  certbot python3-certbot-nginx fail2ban ufw git

# Firewall
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable

# MySQL setup
mysql -e "CREATE DATABASE ${DB_NAME};"
mysql -e "CREATE USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';"
mysql -e "GRANT ALL ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"

# Save credentials securely
cat > /root/.db-credentials > /var/log/cloud-init-custom.log

Debugging Cloud-Init

# Check cloud-init status
cloud-init status

# View the cloud-init log
cat /var/log/cloud-init-output.log

# View detailed cloud-init logs
cat /var/log/cloud-init.log

# Re-run cloud-init (for testing)
sudo cloud-init clean
sudo cloud-init init
sudo cloud-init modules --mode=config
sudo cloud-init modules --mode=final

Best Practices

  1. Test locally first — Use multipass or LXD to test your cloud-init configs before deploying
  2. Use set -euo pipefail — Makes bash scripts fail on errors instead of continuing
  3. Log everything — Redirect output to a log file for debugging
  4. Don't hardcode secrets — Generate passwords dynamically with openssl rand
  5. Keep it idempotent — Scripts should be safe to run multiple times
  6. Start simple — Get the basics working before adding complexity

Was this article helpful?