Custom Cloud-Init Scripts
Automate your VPS setup by providing a custom cloud-init script during creation. Your script runs after the platform finishes configuring networking, security, and SSH access.
Overview
Custom cloud-init lets you:
- Install packages automatically on first boot
- Configure services and daemons
- Set up users and SSH keys
- Deploy applications
- Run any shell commands as root
Scripts run once during the initial boot (or after a reinstall) and execute as the root user.
Supported Formats
Shell Script
Write a standard shell script. The platform saves it to /tmp/custom-cloud-init.sh and runs it with bash after all platform initialization is complete.
#!/bin/bash
set -e
apt update -y
apt install -y nginx certbot python3-certbot-nginx
systemctl enable nginx
systemctl start nginx
Cloud-Config YAML
Provide a #cloud-config YAML document. The platform merges supported sections into its own cloud-init configuration. Your commands always run last.
#cloud-config
packages:
- nginx
- certbot
runcmd:
- systemctl enable nginx
- systemctl start nginx
write_files:
- path: /etc/motd
content: "Welcome to my server!\n"
Supported cloud-config sections:
| Section | Description |
|---|---|
packages | APT/DNF packages to install |
runcmd | Commands to run (after platform init) |
write_files | Files to create on disk |
bootcmd | Commands to run very early in boot |
Validation & Warnings
When you enter a script, the platform runs a warning analyzer that flags likely mistakes before you deploy — for example a missing #! shebang or #cloud-config header, interactive commands that could hang the boot (such as apt without -y), or enabling a restrictive firewall that would cut off DNS and package downloads. Review any warnings and fix them before creating or reinstalling the VPS, since the script only runs on first boot.
Limits
- Maximum 10,000 characters
- Only runs on first boot (or reinstall)
- Must be compatible with your chosen OS (e.g.,
aptfor Ubuntu/Debian,dnffor Fedora/AlmaLinux)
Execution Order
Your custom script runs at the end of the cloud-init lifecycle:
- Platform configures networking (public IP, gateway, DNS)
- Platform hardens security (SSH, fail2ban, firewall rules)
- Platform sets up monitoring agent (qemu-guest-agent)
- Marketplace app scripts run (if applicable)
- Your custom cloud-init runs here
- Cloud-init marks as
done
Monitoring Progress
When you provide a custom cloud-init script, the platform installs a cloud-init-progress helper on your VPS.
Login Banner
When you SSH in while cloud-init is still running, you'll see:
==============================================
System initialization is in progress...
Package installations may be running.
Please wait before using apt/dnf/apk.
Run: cloud-init status --wait
Run: cloud-init-progress
==============================================
cloud-init-progress Command
Run cloud-init-progress to monitor your script's execution:
cloud-init-progress
While running — streams the cloud-init output log in real time (press Ctrl+C to stop watching):
==============================================
Cloud-Init Progress Monitor
==============================================
Status: RUNNING
Following cloud-init output live...
Press Ctrl+C to stop watching.
----------------------------------------
Setting up nginx (1.24.0-2ubuntu7) ...
Processing triggers for man-db ...
...
When complete — shows final status and the last lines of the log:
==============================================
Cloud-Init Progress Monitor
==============================================
Status: COMPLETED SUCCESSFULLY
Last 10 lines of cloud-init log:
----------------------------------------
Setting up certbot (2.9.0-1) ...
Created symlink ...
Cloud-init v. 25.2 finished at Mon, 09 Mar ...
==============================================
If errors occurred — shows the error status and points you to the full log:
Status: COMPLETED WITH ERRORS
Full log: /var/log/cloud-init-output.log
Errors: cloud-init status --long
Other Useful Commands
| Command | Description |
|---|---|
cloud-init status | Show current cloud-init status (running/done/error) |
cloud-init status --wait | Wait for custom setup and first-boot security hardening to finish |
cloud-init status --long | Show detailed status with error messages |
cat /var/log/cloud-init-output.log | View full cloud-init output log |
cat /var/log/cloud-init.log | View cloud-init internal debug log |
dd-hardening-status | Show first-boot security setup progress |
Installing Packages After First Boot
SSH becomes available before initialization finishes. On newly provisioned VPS instances, security package installation and upgrades run after marketplace and custom scripts, and cloud-init waits for them to finish. Before installing packages yourself, run:
cloud-init status --wait
sudo apt-get install curl
If you paused security setup with dd-hardening-pause, run sudo dd-hardening-resume so initialization can finish. Use dd-hardening-status to check progress; recent provisioning scripts record the hardening log at /var/log/dd-hardening.log.
VPS instances created with older provisioning scripts may report status: done while detached security setup still holds the package-manager lock. On those instances, check dd-hardening-status if available and let package operations finish before retrying. Older scripts log to /tmp/security-hardening.log. Do not delete the dpkg lock files.
Examples
Docker Installation
#!/bin/bash
set -e
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
# Install Docker Compose
apt install -y docker-compose-plugin
# Allow non-root docker usage (if you create a user)
# usermod -aG docker yourusername
K3s (Lightweight Kubernetes)
#!/bin/bash
set -e
curl -sfL https://get.k3s.io | sh -
# Wait for K3s to be ready
kubectl wait --for=condition=Ready node --all --timeout=120s
LAMP Stack
#!/bin/bash
set -e
export DEBIAN_FRONTEND=noninteractive
apt update -y
apt install -y apache2 mysql-server php php-mysql libapache2-mod-php
systemctl enable apache2 mysql
systemctl start apache2 mysql
Node.js Application
#cloud-config
packages:
- curl
- git
runcmd:
- curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
- apt install -y nodejs
- npm install -g pm2
Tips
- Use
set -ein shell scripts so the script stops on the first error rather than continuing with a broken state. - Use
export DEBIAN_FRONTEND=noninteractiveto preventaptfrom prompting for user input (which would hang cloud-init). - Don't enable UFW/firewalld unless you understand the platform networking. The platform already configures routes for internal platform services and DNS. A restrictive firewall can break DNS resolution and package downloads.
- Long scripts are fine — there is no hard timeout on cloud-init execution. A full desktop environment install may take 10-20 minutes on a small VPS.
- Test on a cheap instance first — use a Nano plan to verify your script works before deploying to production.
Troubleshooting
Script didn't run
- Verify the VPS status is
Running - SSH in and check:
cloud-init status --long - Look for errors:
cat /var/log/cloud-init.log | grep -i error
Script failed partway
- Check the output log:
cat /var/log/cloud-init-output.log - The log shows exactly which command failed and why
- Fix the script and reinstall the VPS (cloud-init only runs once)
Packages failed to install
- Ensure you used the correct package manager for your OS
- Check that
apt updateordnf makecacheruns before installing - Verify the package names are correct for your OS version
Can't reach the internet during script
- Don't enable UFW or iptables rules that block outbound traffic
- The platform configures the VPS to use its internal DNS resolver — don't overwrite
/etc/resolv.conf
Next Steps
- Creating VPS — Full VPS creation guide
- Managing VPS — Post-creation management
- SSH Access — Connecting to your VPS
- Terraform Provider — Automate VPS creation with
custom_cloud_init