> For the complete documentation index, see [llms.txt](https://docs.i3d.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.i3d.net/compute/flexvm/cloud-init-user-data.md).

# Cloud-init user-data

When you create a VM you can pass a `user_data` object along with the request. Its contents are handed to the VM as cloud-init user-data and processed by the guest **on first boot only** — it is not re-applied on later reboots.

Use it to create users, install SSH keys, write files, install packages, or run a provisioning script, so that a VM comes up ready for your workload without a second configuration pass.

User-data is supported on both **Linux** and **Windows** images. See [Windows images](#windows-images) for the specifics.

## Passing user-data

Add `user_data` to the create-VM request body:

```http
POST /v3/flexVM/clouds/{cloudUuid}/vms
Host: api.i3d.net
PRIVATE-TOKEN: YOUR_API_KEY
Content-Type: application/json

{
  "name": "development.ubuntu-2404",
  "description": "DB1 VM",
  "image_name": "ubuntu-2404-server-amd64",
  "instance_type_name": "vm.gpu.1rtx4000.30c.240g",
  "user_data": {
    "data": "#cloud-config\nusers:\n  - name: deploy\n    sudo: ALL=(ALL) NOPASSWD:ALL\n    shell: /bin/bash\n    ssh_authorized_keys:\n      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwdgjY0AlmkeLknBpoVmJg/quNSifyBHEK1MREpV4Ri john.doe@i3d.net\npackage_update: true\npackages:\n  - htop\n",
    "is_base64": false
  }
}
```

For a Terraform example, see [Using Terraform](/compute/flexvm/using-terraform.md#3-creating-a-vm) — the provider takes the user-data from a file through `user_data_file`.

### `ssh_keys` or `user_data`

`ssh_keys` and `user_data` are two alternative ways to get access to a new VM, and they cannot be combined:

| Request          | Result                                                                |
| ---------------- | --------------------------------------------------------------------- |
| `ssh_keys` only  | FlexVM generates the cloud-init configuration and installs your keys. |
| `user_data` only | Your user-data is used as-is. You configure access yourself.          |
| Both             | Rejected with `422`.                                                  |
| Neither          | Rejected with `422` on Linux images. Allowed on Windows images.       |

{% hint style="warning" %}
**Providing `user_data` replaces the cloud-init configuration FlexVM generates for you** — the one that creates the default user and installs the keys from `ssh_keys`. Anything you need for access must be in your own user-data: the user account, its SSH keys, and/or its password. If you omit them, the VM will boot and run but you will not be able to log in, and user-data cannot be changed afterwards.
{% endhint %}

Your VM's network configuration (IP address, prefix, and gateway from your Tenant VLAN) is applied separately by FlexVM and is *not* affected by custom user-data. See [Network Configuration](/compute/flexvm/network-configuration.md).

## Format and size

`user_data.data` accepts any format cloud-init itself supports — most commonly a `#cloud-config` document or a shell script. See the cloud-init [user-data formats](https://docs.cloud-init.io/en/latest/explanation/format/index.html) and [example configurations](https://docs.cloud-init.io/en/latest/reference/examples.html#yaml-examples).

* Length must be between **1 and 64000 characters**.
* Set `is_base64` to `true` when `data` is base64-encoded. Use this for content that cannot be carried as JSON text — for example [gzip-compressed](https://docs.cloud-init.io/en/latest/explanation/format/gzip.html#gzip) user-data, which is also a way to stay within the size limit. Default is `false`. If `is_base64` is `true` and `data` is not valid base64, the request is rejected with a `400`.

## Write-only and immutable

* User-data is **write-only**: it is never returned by the GET VM endpoints. Keep your own copy of what you sent — for example the file you feed to Terraform's `user_data_file`.
* It is **set at creation only**. To change a VM's user-data, delete the VM and create a new one.
* It is stored encrypted at rest, so it is a valid place for tokens or passwords your first-boot configuration needs. As with any provisioning data, prefer short-lived credentials.

## Linux images

Linux images run cloud-init, so `#cloud-config` and script formats work as documented upstream. A minimal configuration that keeps you in control of access:

```yaml
#cloud-config
hostname: build-agent-01
fqdn: build-agent-01
manage_etc_hosts: true
users:
  - name: deploy
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwdgjY0AlmkeLknBpoVmJg/quNSifyBHEK1MREpV4Ri john.doe@i3d.net
package_update: true
packages:
  - docker.io
runcmd:
  - systemctl enable --now docker
```

## Windows images

i3D.net Windows images ship with [Cloudbase-Init](https://cloudbase-init.readthedocs.io/en/latest/), the Windows implementation of cloud-init, so the same `user_data` field applies.

* **`user_data` is optional on Windows.** i3D.net Windows images are created with a default `Administrator` password, which i3D.net provides to you, so a Windows VM is reachable without any user-data. `ssh_keys` has no effect on Windows images.
* Supply `user_data` when you want the VM configured on first boot anyway — to rotate that default password, add accounts, enable a remote-access method, or run your own provisioning script.
* Cloudbase-Init supports a subset of `#cloud-config` plus plain scripts (PowerShell, batch, bash). A PowerShell script is the most predictable option; see the [Cloudbase-Init user-data documentation](https://cloudbase-init.readthedocs.io/en/latest/userdata.html) for what each format covers.
* **Windows Firewall is on by default and blocks inbound SSH.** Windows OpenSSH listens on port 22, the same as Linux, but nothing reaches it until an inbound rule allows it *for the firewall profile the VM's network is in*. Windows classifies an unidentified network as **Public**, so a rule scoped to Private or Domain has no effect and SSH still times out even though `sshd` is running. The examples below scope the rule to `Any` for that reason.

{% hint style="info" %}
**Allow several minutes before the VM answers.** A Windows VM reports `running` well before Cloudbase-Init has finished with it. The network configuration is applied partway through first boot — the interface switches from DHCP to its static address at that point — and a script that installs the OpenSSH server capability adds a few minutes more on top.

In our testing, a Windows VM first accepted an SSH connection **roughly 7 minutes** after boot. Connections attempted before that are refused or time out, which looks identical to a misconfigured VM. Wait out the first ten minutes or so before you start troubleshooting access.
{% endhint %}

### Example: enable OpenSSH with a key, without a password

This variant sets no password at all, so nothing secret goes into the request body. Note that members of the Administrators group share one authorized-keys file, and OpenSSH ignores that file unless its permissions are tightened:

```powershell
#ps1_sysnative
# Enable the OpenSSH server. Starting it also creates C:\ProgramData\ssh.
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd

# Open port 22 for every firewall profile. The capability install adds a rule of
# its own, but it may be missing or scoped to a profile the VM is not using.
if (Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue) {
  Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Enabled True -Profile Any
} else {
  New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' `
    -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -Profile Any
}

# Administrators authorize keys here, not in %USERPROFILE%\.ssh\authorized_keys.
$keyFile = "$env:ProgramData\ssh\administrators_authorized_keys"
Set-Content -Path $keyFile -Encoding ascii `
  -Value "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwdgjY0AlmkeLknBpoVmJg/quNSifyBHEK1MREpV4Ri john.doe@i3d.net"

# OpenSSH rejects the file unless only Administrators and SYSTEM can write it.
icacls.exe $keyFile /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
```

{% hint style="info" %}
This adds key-based SSH access but leaves the image's default `Administrator` password in place, so password logins (for example over RDP) remain possible with it. Rotate or disable that password separately if you want key-only access to be the only way in.
{% endhint %}

### Example: enable OpenSSH, rotate the default Administrator password

```powershell
#ps1_sysnative
# Set the local Administrator password and enable OpenSSH.
$password = ConvertTo-SecureString "REPLACE_WITH_A_STRONG_PASSWORD" -AsPlainText -Force
Set-LocalUser -Name "Administrator" -Password $password

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd

if (Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue) {
  Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Enabled True -Profile Any
} else {
  New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' `
    -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -Profile Any
}
```

### Example: Enable OpenSSH, RDP and rotate the default Administrator password

```powershell
#ps1_sysnative
# Set the local Administrator password.
$password = ConvertTo-SecureString "REPLACE_WITH_A_STRONG_PASSWORD" -AsPlainText -Force
Set-LocalUser -Name "Administrator" -Password $password

# --- Enable Remote Desktop (RDP) ---
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name 'fDenyTSConnections' -Value 0
Enable-NetFirewallRule -DisplayGroup 'Remote Desktop'
Set-NetFirewallRule -DisplayGroup 'Remote Desktop' -Enabled True -Profile Any
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 1

# --- Enable the OpenSSH server too ---
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd

if (Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue) {
  Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Enabled True -Profile Any
} else {
  New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' `
    -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -Profile Any
}
```

### Example: enable WinRM, rotate the default Administrator password

WinRM is the transport behind PowerShell Remoting (`Enter-PSSession`, `Invoke-Command`) and Ansible's `winrm` connection. The service is installed on every Windows image but is not listening, and the same restrictive firewall applies to its port 5985 as to SSH:

```powershell
#ps1_sysnative
# Set the local Administrator password.
$password = ConvertTo-SecureString "REPLACE_WITH_A_STRONG_PASSWORD" -AsPlainText -Force
Set-LocalUser -Name "Administrator" -Password $password

# --- Enable WinRM ---
# -SkipNetworkProfileCheck is required: without it Enable-PSRemoting refuses to
# add its firewall exception when the network is classified as Public, which is
# how Windows sees the VM's unidentified network.
Enable-PSRemoting -Force -SkipNetworkProfileCheck
Set-Service -Name WinRM -StartupType Automatic
Start-Service WinRM

# Open port 5985 for every firewall profile and any remote address. Enable-PSRemoting
# leaves a Public-profile rule of its own behind, but that one only accepts the
# local subnet, so it does not cover connections from your own network.
if (Get-NetFirewallRule -Name 'WINRM-HTTP-In-TCP' -ErrorAction SilentlyContinue) {
  Set-NetFirewallRule -Name 'WINRM-HTTP-In-TCP' -Enabled True -Profile Any -RemoteAddress 'YOUR_IP_OR_CIDR'
} else {
  New-NetFirewallRule -Name 'WINRM-HTTP-In-TCP' -DisplayName 'Windows Remote Management (HTTP-In)' `
    -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 5985 -Profile Any -RemoteAddress 'YOUR_IP_OR_CIDR'
}
```

Because the VM is not domain-joined, your client has to be told to trust it before it will send credentials over HTTP:

```powershell
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'VM_IP_ADDRESS' -Concatenate -Force
Enter-PSSession -ComputerName VM_IP_ADDRESS -Credential Administrator
```

{% hint style="info" %}
Port 5985 is HTTP, but the payload is still encrypted by the Negotiate/NTLM session, so `AllowUnencrypted` does not need to be turned on — leave it off. Because HTTP WinRM does not validate the server identity, use 5985 only on trusted networks/VPN; for exposure beyond that, prefer a TLS listener on 5986 (requires a certificate in the image or installed by the script).

This example authenticates as the built-in `Administrator`. Any *other* local administrator is filtered by remote UAC over WinRM and comes back as access denied; if you provision one, also set `LocalAccountTokenFilterPolicy` to `1` under `HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System`.
{% endhint %}

### Bringing your own Windows image

You can supply your own Windows image. Cloudbase-Init must be installed in it for `user_data` to be processed — if it isn't, the field is ignored and the image must already contain whatever access method you intend to use. Preparing your own image also means the default-`Administrator`-password behaviour above does not apply: your image defines its own initial access. Contact i3D.net support to have a custom image onboarded.

## Troubleshooting

If a VM reaches `running` but you cannot log in, the most likely cause is user-data that did not configure access. See [Cannot SSH into a running VM](/compute/flexvm/troubleshooting.md#cannot-ssh-into-a-running-vm).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.i3d.net/compute/flexvm/cloud-init-user-data.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
