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.

Bash
#!/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.

YAML
#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:

SectionDescription
packagesAPT/DNF packages to install
runcmdCommands to run (after platform init)
write_filesFiles to create on disk
bootcmdCommands 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., apt for Ubuntu/Debian, dnf for Fedora/AlmaLinux)

Execution Order

Your custom script runs at the end of the cloud-init lifecycle:

  1. Platform configures networking (public IP, gateway, DNS)
  2. Platform hardens security (SSH, fail2ban, firewall rules)
  3. Platform sets up monitoring agent (qemu-guest-agent)
  4. Marketplace app scripts run (if applicable)
  5. Your custom cloud-init runs here
  6. 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:

Text
==============================================
  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:

Bash
cloud-init-progress

While running — streams the cloud-init output log in real time (press Ctrl+C to stop watching):

Text
==============================================
  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:

Text
==============================================
  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:

Text
  Status: COMPLETED WITH ERRORS

  Full log: /var/log/cloud-init-output.log
  Errors:   cloud-init status --long

Other Useful Commands

CommandDescription
cloud-init statusShow current cloud-init status (running/done/error)
cloud-init status --waitWait for custom setup and first-boot security hardening to finish
cloud-init status --longShow detailed status with error messages
cat /var/log/cloud-init-output.logView full cloud-init output log
cat /var/log/cloud-init.logView cloud-init internal debug log
dd-hardening-statusShow 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:

Bash
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

Bash
#!/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)

Bash
#!/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

Bash
#!/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

YAML
#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 -e in shell scripts so the script stops on the first error rather than continuing with a broken state.
  • Use export DEBIAN_FRONTEND=noninteractive to prevent apt from 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

  1. Verify the VPS status is Running
  2. SSH in and check: cloud-init status --long
  3. Look for errors: cat /var/log/cloud-init.log | grep -i error

Script failed partway

  1. Check the output log: cat /var/log/cloud-init-output.log
  2. The log shows exactly which command failed and why
  3. 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 update or dnf makecache runs 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