{"slug":"vps-cloud-init","title":"Custom Cloud-Init Scripts","description":"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.","section":"Features","url":"https://docs.danubedata.ro/vps-cloud-init","markdown_url":"https://docs.danubedata.ro/vps-cloud-init.md","breadcrumbs":[{"title":"Features","slug":null},{"title":"VPS Instances","slug":"vps-overview"},{"title":"Custom Cloud-Init","slug":"vps-cloud-init"}],"headings":[{"level":1,"title":"Custom Cloud-Init Scripts","id":"custom-cloud-init-scripts"},{"level":2,"title":"Overview","id":"overview"},{"level":2,"title":"Supported Formats","id":"supported-formats"},{"level":3,"title":"Shell Script","id":"shell-script"},{"level":3,"title":"Cloud-Config YAML","id":"cloud-config-yaml"},{"level":2,"title":"Validation & Warnings","id":"validation-warnings"},{"level":2,"title":"Limits","id":"limits"},{"level":2,"title":"Execution Order","id":"execution-order"},{"level":2,"title":"Monitoring Progress","id":"monitoring-progress"},{"level":3,"title":"Login Banner","id":"login-banner"},{"level":3,"title":"cloud-init-progress Command","id":"cloud-init-progress-command"},{"level":3,"title":"Other Useful Commands","id":"other-useful-commands"},{"level":3,"title":"Installing Packages After First Boot","id":"installing-packages-after-first-boot"},{"level":2,"title":"Examples","id":"examples"},{"level":3,"title":"Docker Installation","id":"docker-installation"},{"level":3,"title":"K3s (Lightweight Kubernetes)","id":"k3s-lightweight-kubernetes"},{"level":3,"title":"LAMP Stack","id":"lamp-stack"},{"level":3,"title":"Node.js Application","id":"nodejs-application"},{"level":2,"title":"Tips","id":"tips"},{"level":2,"title":"Troubleshooting","id":"troubleshooting"},{"level":3,"title":"Script didn't run","id":"script-didnt-run"},{"level":3,"title":"Script failed partway","id":"script-failed-partway"},{"level":3,"title":"Packages failed to install","id":"packages-failed-to-install"},{"level":3,"title":"Can't reach the internet during script","id":"cant-reach-the-internet-during-script"},{"level":2,"title":"Next Steps","id":"next-steps"}],"format":"markdown","word_count":1164,"content":"# Custom Cloud-Init Scripts\n\nAutomate 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.\n\n## Overview\n\nCustom cloud-init lets you:\n\n- Install packages automatically on first boot\n- Configure services and daemons\n- Set up users and SSH keys\n- Deploy applications\n- Run any shell commands as root\n\nScripts run **once** during the initial boot (or after a reinstall) and execute as the `root` user.\n\n## Supported Formats\n\n### Shell Script\n\nWrite 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.\n\n```bash\n#!/bin/bash\nset -e\n\napt update -y\napt install -y nginx certbot python3-certbot-nginx\n\nsystemctl enable nginx\nsystemctl start nginx\n```\n\n### Cloud-Config YAML\n\nProvide a `#cloud-config` YAML document. The platform merges supported sections into its own cloud-init configuration. Your commands always run last.\n\n```yaml\n#cloud-config\npackages:\n  - nginx\n  - certbot\n\nruncmd:\n  - systemctl enable nginx\n  - systemctl start nginx\n\nwrite_files:\n  - path: /etc/motd\n    content: \"Welcome to my server!\\n\"\n```\n\n**Supported cloud-config sections:**\n\n| Section | Description |\n|---------|-------------|\n| `packages` | APT/DNF packages to install |\n| `runcmd` | Commands to run (after platform init) |\n| `write_files` | Files to create on disk |\n| `bootcmd` | Commands to run very early in boot |\n\n## Validation & Warnings\n\nWhen 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.\n\n## Limits\n\n- Maximum **10,000 characters**\n- Only runs on **first boot** (or reinstall)\n- Must be compatible with your chosen OS (e.g., `apt` for Ubuntu/Debian, `dnf` for Fedora/AlmaLinux)\n\n## Execution Order\n\nYour custom script runs at the end of the cloud-init lifecycle:\n\n1. Platform configures networking (public IP, gateway, DNS)\n2. Platform hardens security (SSH, fail2ban, firewall rules)\n3. Platform sets up monitoring agent (qemu-guest-agent)\n4. Marketplace app scripts run (if applicable)\n5. **Your custom cloud-init runs here**\n6. Cloud-init marks as `done`\n\n## Monitoring Progress\n\nWhen you provide a custom cloud-init script, the platform installs a `cloud-init-progress` helper on your VPS.\n\n### Login Banner\n\nWhen you SSH in while cloud-init is still running, you'll see:\n\n```\n==============================================\n  System initialization is in progress...\n  Package installations may be running.\n  Please wait before using apt/dnf/apk.\n\n  Run: cloud-init status --wait\n  Run: cloud-init-progress\n==============================================\n```\n\n### cloud-init-progress Command\n\nRun `cloud-init-progress` to monitor your script's execution:\n\n```bash\ncloud-init-progress\n```\n\n**While running** — streams the cloud-init output log in real time (press `Ctrl+C` to stop watching):\n\n```\n==============================================\n  Cloud-Init Progress Monitor\n==============================================\n\n  Status: RUNNING\n  Following cloud-init output live...\n  Press Ctrl+C to stop watching.\n\n  ----------------------------------------\n\nSetting up nginx (1.24.0-2ubuntu7) ...\nProcessing triggers for man-db ...\n...\n```\n\n**When complete** — shows final status and the last lines of the log:\n\n```\n==============================================\n  Cloud-Init Progress Monitor\n==============================================\n\n  Status: COMPLETED SUCCESSFULLY\n\n  Last 10 lines of cloud-init log:\n  ----------------------------------------\n  Setting up certbot (2.9.0-1) ...\n  Created symlink ...\n  Cloud-init v. 25.2 finished at Mon, 09 Mar ...\n\n==============================================\n```\n\n**If errors occurred** — shows the error status and points you to the full log:\n\n```\n  Status: COMPLETED WITH ERRORS\n\n  Full log: /var/log/cloud-init-output.log\n  Errors:   cloud-init status --long\n```\n\n### Other Useful Commands\n\n| Command | Description |\n|---------|-------------|\n| `cloud-init status` | Show current cloud-init status (running/done/error) |\n| `cloud-init status --wait` | Wait for custom setup and first-boot security hardening to finish |\n| `cloud-init status --long` | Show detailed status with error messages |\n| `cat /var/log/cloud-init-output.log` | View full cloud-init output log |\n| `cat /var/log/cloud-init.log` | View cloud-init internal debug log |\n| `dd-hardening-status` | Show first-boot security setup progress |\n\n### Installing Packages After First Boot\n\nSSH 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:\n\n```bash\ncloud-init status --wait\nsudo apt-get install curl\n```\n\nIf 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`.\n\nVPS 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.\n\n## Examples\n\n### Docker Installation\n\n```bash\n#!/bin/bash\nset -e\n\ncurl -fsSL https://get.docker.com | sh\nsystemctl enable docker\nsystemctl start docker\n\n# Install Docker Compose\napt install -y docker-compose-plugin\n\n# Allow non-root docker usage (if you create a user)\n# usermod -aG docker yourusername\n```\n\n### K3s (Lightweight Kubernetes)\n\n```bash\n#!/bin/bash\nset -e\n\ncurl -sfL https://get.k3s.io | sh -\n\n# Wait for K3s to be ready\nkubectl wait --for=condition=Ready node --all --timeout=120s\n```\n\n### LAMP Stack\n\n```bash\n#!/bin/bash\nset -e\n\nexport DEBIAN_FRONTEND=noninteractive\n\napt update -y\napt install -y apache2 mysql-server php php-mysql libapache2-mod-php\n\nsystemctl enable apache2 mysql\nsystemctl start apache2 mysql\n```\n\n### Node.js Application\n\n```yaml\n#cloud-config\npackages:\n  - curl\n  - git\n\nruncmd:\n  - curl -fsSL https://deb.nodesource.com/setup_22.x | bash -\n  - apt install -y nodejs\n  - npm install -g pm2\n```\n\n## Tips\n\n- **Use `set -e`** in shell scripts so the script stops on the first error rather than continuing with a broken state.\n- **Use `export DEBIAN_FRONTEND=noninteractive`** to prevent `apt` from prompting for user input (which would hang cloud-init).\n- **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.\n- **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.\n- **Test on a cheap instance first** — use a Nano plan to verify your script works before deploying to production.\n\n## Troubleshooting\n\n### Script didn't run\n\n1. Verify the VPS status is `Running`\n2. SSH in and check: `cloud-init status --long`\n3. Look for errors: `cat /var/log/cloud-init.log | grep -i error`\n\n### Script failed partway\n\n1. Check the output log: `cat /var/log/cloud-init-output.log`\n2. The log shows exactly which command failed and why\n3. Fix the script and **reinstall** the VPS (cloud-init only runs once)\n\n### Packages failed to install\n\n- Ensure you used the correct package manager for your OS\n- Check that `apt update` or `dnf makecache` runs before installing\n- Verify the package names are correct for your OS version\n\n### Can't reach the internet during script\n\n- Don't enable UFW or iptables rules that block outbound traffic\n- The platform configures the VPS to use its internal DNS resolver — don't overwrite `/etc/resolv.conf`\n\n## Next Steps\n\n- [Creating VPS](https://docs.danubedata.ro/vps-creating) — Full VPS creation guide\n- [Managing VPS](https://docs.danubedata.ro/vps-managing) — Post-creation management\n- [SSH Access](https://docs.danubedata.ro/vps-ssh) — Connecting to your VPS\n- [Terraform Provider](https://docs.danubedata.ro/terraform-resources) — Automate VPS creation with `custom_cloud_init`\n","prev":{"title":"Resource Profiles","slug":"vps-profiles","url":"https://docs.danubedata.ro/vps-profiles","markdown_url":"https://docs.danubedata.ro/vps-profiles.md","json_url":"https://docs.danubedata.ro/vps-profiles.json"},"next":{"title":"SSH Access","slug":"vps-ssh","url":"https://docs.danubedata.ro/vps-ssh","markdown_url":"https://docs.danubedata.ro/vps-ssh.md","json_url":"https://docs.danubedata.ro/vps-ssh.json"},"index_url":"https://docs.danubedata.ro/index.json"}