Cloud-init and Cloudbase-init¶
Cloud-init is the industry-standard tool for configuring cloud instances at first boot. When you launch an instance in Safespring Compute, cloud-init (Linux) or cloudbase-init (Windows) reads the User Data you provide and executes the configuration automatically — before you ever log in.
You supply User Data in the "Configuration" tab of the Launch Instance dialog in Horizon, or via the --user-data flag with the OpenStack CLI:
openstack server create \ --image "Ubuntu 24.04" \ --flavor l2.c2r4 \ --user-data my-script.yaml \ my-instance
Linux (cloud-init)¶
Linux images on Safespring ship with cloud-init pre-installed. Scripts use the YAML-based #cloud-config format. The cloud-init run happens once, on the very first boot of the instance.
Switch to Swedish package mirrors¶
Ubuntu images in Safespring come with a default mirror (nova.clouds.archive.ubuntu.com) that may be slower than the Swedish mirror when accessed from our Stockholm and Sundsvall regions. This snippet replaces the mirror and runs an initial package update.
#cloud-config runcmd: - sed -i 's|nova.clouds.archive.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list.d/ubuntu.sources - sed -i 's|nova.clouds.archive.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list - sed -i 's|security.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list.d/ubuntu.sources - sed -i 's|security.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list - apt-get update
Install NetBird and join a network¶
NetBird is a WireGuard-based overlay network. Replace YOUR_SETUP_KEY_HERE with the setup key from your NetBird management console.
#cloud-config runcmd: - curl -fsSL https://pkgs.netbird.io/install.sh | sh - netbird up --setup-key YOUR_SETUP_KEY_HERE
Switch to Swedish mirrors, update all packages, and install NetBird¶
A combined snippet that first optimizes the mirror, performs a full system upgrade, and then joins a NetBird network.
#cloud-config runcmd: # Switch to Swedish mirror - sed -i 's|nova.clouds.archive.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list.d/ubuntu.sources - sed -i 's|nova.clouds.archive.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list - sed -i 's|security.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list.d/ubuntu.sources - sed -i 's|security.ubuntu.com|se.archive.ubuntu.com|g' /etc/apt/sources.list # Update and upgrade all packages - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y - DEBIAN_FRONTEND=noninteractive apt-get autoremove -y # Install NetBird and join the network - curl -fsSL https://pkgs.netbird.io/install.sh | sh - netbird up --setup-key YOUR_SETUP_KEY_HERE
Windows (cloudbase-init)¶
Safespring Windows images come with cloudbase-init pre-installed. Cloudbase-init is the Windows equivalent of cloud-init and can run PowerShell scripts supplied as User Data. Scripts must begin with the directive #ps1_sysnative so that cloudbase-init executes them in the 64-bit PowerShell host.
Warning
Anything entered in the User Data / Customization Script field may end up in cloudbase-init log files on the instance. Do not reuse passwords set here for long-lived production accounts — treat them as temporary bootstrap credentials and rotate them after first login.
Important: always set the Administrator password first¶
Unlike Linux, Windows instances are not accessible over SSH by default after launch. The built-in Administrator account has no password set, meaning you cannot log in via RDP until one is configured. Without a password, the only way into a fresh instance is through the OpenStack web console — which is inconvenient and does not scale.
Every Windows cloudbase-init script should therefore begin by setting the Administrator password. The block below is the canonical password-setting snippet that the more complete examples in the following sections all build upon.
#ps1_sysnative $NewPassword = "YourPasswordHere" $ErrorActionPreference = "Stop" try { $account = [ADSI]"WinNT://./Administrator,user" $account.SetPassword($NewPassword) # Enable the account and clear "must change password at next logon" $flags = $account.UserFlags.value $flags = $flags -band (-bnot 0x2) # ADS_UF_ACCOUNTDISABLE — clear disabled flag $flags = $flags -bor 0x10000 # ADS_UF_DONT_EXPIRE_PASSWD $account.UserFlags = $flags $account.PasswordExpired = 0 $account.SetInfo() Write-Host "Administrator password set successfully." } catch { Write-Host "Error setting password: $_" exit 1 }
Enable OpenSSH Server¶
Installs the Windows built-in OpenSSH Server capability, starts it automatically, opens port 22 in the Windows Firewall, and sets PowerShell as the default remote shell. Includes the mandatory Administrator password block at the top.
#ps1_sysnative $NewPassword = "YourPasswordHere" $ErrorActionPreference = "Stop" try { # Set Administrator password $account = [ADSI]"WinNT://./Administrator,user" $account.SetPassword($NewPassword) $flags = $account.UserFlags.value $flags = $flags -band (-bnot 0x2) # ADS_UF_ACCOUNTDISABLE $flags = $flags -bor 0x10000 # ADS_UF_DONT_EXPIRE_PASSWD $account.UserFlags = $flags $account.PasswordExpired = 0 $account.SetInfo() Write-Host "Administrator password set successfully." # Install OpenSSH Server Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 Set-Service -Name sshd -StartupType Automatic Start-Service sshd # Open firewall (rule may already exist, so errors are suppressed) New-NetFirewallRule -DisplayName "OpenSSH Server" ` -Direction Inbound ` -Protocol TCP ` -LocalPort 22 ` -Action Allow ` -Profile Any ` -ErrorAction SilentlyContinue # Set default shell to PowerShell New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" ` -Name DefaultShell ` -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" ` -PropertyType String ` -Force Write-Host "OpenSSH Server installed and configured successfully." } catch { Write-Host "Error: $_" exit 1 }
Run Windows Update and install NetBird¶
Applies all pending Windows updates, installs NetBird, and joins a NetBird network. Replace YOUR-SETUP-KEY-HERE with your NetBird setup key. Includes the mandatory Administrator password block at the top.
#ps1_sysnative $NewPassword = "YourPasswordHere" $SetupKey = "YOUR-SETUP-KEY-HERE" $InstallerPath = "C:\Windows\Temp\netbird-installer.exe" $DownloadUrl = "https://pkgs.netbird.io/windows/x64/NetBird_Installer_latest.exe" $ErrorActionPreference = "Stop" try { # Set Administrator password $account = [ADSI]"WinNT://./Administrator,user" $account.SetPassword($NewPassword) $flags = $account.UserFlags.value $flags = $flags -band (-bnot 0x2) # ADS_UF_ACCOUNTDISABLE $flags = $flags -bor 0x10000 # ADS_UF_DONT_EXPIRE_PASSWD $account.UserFlags = $flags $account.PasswordExpired = 0 $account.SetInfo() Write-Host "Administrator password set successfully." # Run Windows Updates Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Install-Module PSWindowsUpdate -Force -SkipPublisherCheck Import-Module PSWindowsUpdate Get-WindowsUpdate -Install -AcceptAll -IgnoreReboot Write-Host "Windows updates applied." # Install NetBird [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri $DownloadUrl -OutFile $InstallerPath Start-Process -FilePath $InstallerPath -ArgumentList "/S" -Wait Write-Host "NetBird installed." # Join NetBird network & "C:\Program Files\NetBird\netbird.exe" up --setup-key $SetupKey Write-Host "NetBird network joined." # Reboot if required by Windows Update $rebootPending = (Get-WmiObject -Class Win32_ComputerSystem).RebootPending if ($rebootPending) { Write-Host "Reboot required, restarting." Restart-Computer -Force } } catch { Write-Host "Error: $_" exit 1 }
Join an Active Directory domain¶
Joins the instance to an Active Directory domain and places the computer account in a specific OU. Replace all variables at the top of the script with values from your environment. Includes the mandatory Administrator password block at the top.
#ps1_sysnative $NewPassword = "YourPasswordHere" $DomainName = "corp.example.com" $OUPath = "OU=Servers,DC=corp,DC=example,DC=com" $JoinUser = "corp\joiner-account" $JoinPassword = "YourJoinPasswordHere" $ErrorActionPreference = "Stop" try { # Set Administrator password $account = [ADSI]"WinNT://./Administrator,user" $account.SetPassword($NewPassword) $flags = $account.UserFlags.value $flags = $flags -band (-bnot 0x2) # ADS_UF_ACCOUNTDISABLE $flags = $flags -bor 0x10000 # ADS_UF_DONT_EXPIRE_PASSWD $account.UserFlags = $flags $account.PasswordExpired = 0 $account.SetInfo() Write-Host "Administrator password set successfully." # Join domain $securePassword = ConvertTo-SecureString $JoinPassword -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($JoinUser, $securePassword) Add-Computer -DomainName $DomainName ` -OUPath $OUPath ` -Credential $credential ` -Restart ` -Force Write-Host "Domain join initiated, rebooting." } catch { Write-Host "Error: $_" exit 1 }
Using instance metadata to parameterize scripts¶
The scripts above require you to edit variables like YOUR_SETUP_KEY_HERE before pasting them into the Configuration window. A cleaner approach is to store those values as OpenStack instance metadata properties and have the cloud-init script fetch them at boot from the metadata service (169.254.169.254). The script itself then becomes fully generic and can be reused across any number of instances without modification.
Set the metadata property at launch¶
Pass the property with --property when creating the instance:
openstack server create \ --image "Ubuntu 24.04" \ --flavor l2.c2r4 \ --user-data netbird-generic.yaml \ --property netbird_setup_key=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX \ my-instance
In Horizon, add the key-value pair in the Metadata tab of the Launch Instance wizard.
See Custom metadata for more details on setting and reading instance metadata.
Example: generic NetBird install¶
The script below installs NetBird and reads the setup key from instance metadata at boot. Because the setup key is never written into the User Data, the same script can be stored as a template and launched against any instance without editing.
#cloud-config runcmd: - | SETUP_KEY=$(curl -sf http://169.254.169.254/openstack/latest/meta_data.json \ | python3 -c "import sys,json; print(json.load(sys.stdin)['meta']['netbird_setup_key'])") if [ -z "$SETUP_KEY" ]; then echo "ERROR: netbird_setup_key not found in instance metadata" >&2 exit 1 fi curl -fsSL https://pkgs.netbird.io/install.sh | sh netbird up --setup-key "$SETUP_KEY"
python3 is used here instead of jq because it is available on all Ubuntu cloud images without any additional package installation. The same pattern works for any variable you want to externalize — domain names, environment tags, API tokens, and so on.