diff --git a/virtweb_backend/src/controllers/vm_controller.rs b/virtweb_backend/src/controllers/vm_controller.rs
index ed18742..121a995 100644
--- a/virtweb_backend/src/controllers/vm_controller.rs
+++ b/virtweb_backend/src/controllers/vm_controller.rs
@@ -109,6 +109,28 @@ pub async fn get_single_src_def(client: LibVirtReq, id: web::Path<SingleVMUUidRe
         .body(info))
 }
 
+/// Get the generated cloud init configuration disk of a vm
+pub async fn get_cloud_init_disk(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
+    let info = match client.get_single_domain(id.uid).await {
+        Ok(i) => i,
+        Err(e) => {
+            log::error!("Failed to get domain information! {e}");
+            return Ok(HttpResponse::InternalServerError().json(e.to_string()));
+        }
+    };
+
+    let vm = VMInfo::from_domain(info)?;
+    let disk = vm.cloud_init.generate_nocloud_disk()?;
+
+    Ok(HttpResponse::Ok()
+        .content_type("application/x-iso9660-image")
+        .insert_header((
+            "Content-Disposition",
+            format!("attachment; filename=\"cloud_init_{}.iso\"", vm.name),
+        ))
+        .body(disk))
+}
+
 /// Update a VM information
 pub async fn update(
     client: LibVirtReq,
diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs
index c9b509d..4e5ed94 100644
--- a/virtweb_backend/src/main.rs
+++ b/virtweb_backend/src/main.rs
@@ -202,6 +202,10 @@ async fn main() -> std::io::Result<()> {
                 "/api/vm/{uid}/src",
                 web::get().to(vm_controller::get_single_src_def),
             )
+            .route(
+                "/api/vm/{uid}/cloud_init_disk",
+                web::get().to(vm_controller::get_cloud_init_disk),
+            )
             .route(
                 "/api/vm/{uid}/autostart",
                 web::get().to(vm_controller::get_autostart),
diff --git a/virtweb_backend/src/utils/cloud_init_utils.rs b/virtweb_backend/src/utils/cloud_init_utils.rs
index 1059845..22c96fd 100644
--- a/virtweb_backend/src/utils/cloud_init_utils.rs
+++ b/virtweb_backend/src/utils/cloud_init_utils.rs
@@ -1,3 +1,7 @@
+use crate::app_config::AppConfig;
+use crate::constants;
+use std::process::Command;
+
 /// VM Cloud Init configuration
 ///
 /// RedHat documentation: https://docs.redhat.com/fr/documentation/red_hat_enterprise_linux/9/html/configuring_and_managing_cloud-init_for_rhel_9/configuring-cloud-init_cloud-content
@@ -17,3 +21,55 @@ pub struct CloudInitConfig {
     #[serde(skip_serializing_if = "Option::is_none")]
     network_configuration: Option<String>,
 }
+
+impl CloudInitConfig {
+    /// Generate disk image for nocloud usage
+    pub fn generate_nocloud_disk(&self) -> anyhow::Result<Vec<u8>> {
+        let temp_path = tempfile::tempdir_in(&AppConfig::get().temp_dir)?;
+
+        let mut cmd = Command::new(constants::PROGRAM_CLOUD_LOCALDS);
+
+        // ISO destination path
+        let temp_iso = temp_path.path().join("disk.iso");
+        cmd.arg(&temp_iso);
+
+        // Process network configuration
+        if let Some(net_conf) = &self.network_configuration {
+            let net_conf_path = temp_path.path().join("network");
+            std::fs::write(&net_conf_path, net_conf)?;
+            cmd.arg("--network-config").arg(&net_conf_path);
+        }
+
+        // Process user data
+        let user_data_path = temp_path.path().join("user-data");
+        std::fs::write(&user_data_path, &self.user_data)?;
+        cmd.arg(user_data_path);
+
+        // Process metadata
+        let mut metadatas = vec![];
+        if let Some(inst_id) = &self.instance_id {
+            metadatas.push(format!("instance-id: {}", inst_id));
+        }
+        if let Some(local_hostname) = &self.local_hostname {
+            metadatas.push(format!("local-hostname: {}", local_hostname));
+        }
+        let meta_data_path = temp_path.path().join("meta-data");
+        std::fs::write(&meta_data_path, metadatas.join("\n"))?;
+        cmd.arg(meta_data_path);
+
+        // Execute command
+        let output = cmd.output()?;
+        if !output.status.success() {
+            anyhow::bail!(
+                "{} exited with status {}!\nStdout: {}\nStderr: {}",
+                constants::PROGRAM_CLOUD_LOCALDS,
+                output.status,
+                String::from_utf8_lossy(&output.stdout),
+                String::from_utf8_lossy(&output.stderr)
+            );
+        }
+
+        // Read generated ISO file
+        Ok(std::fs::read(temp_iso)?)
+    }
+}
diff --git a/virtweb_frontend/src/api/VMApi.ts b/virtweb_frontend/src/api/VMApi.ts
index 1e44b24..dbc119c 100644
--- a/virtweb_frontend/src/api/VMApi.ts
+++ b/virtweb_frontend/src/api/VMApi.ts
@@ -82,6 +82,14 @@ export interface VMNetBridge {
   bridge: string;
 }
 
+export interface VMCloudInit {
+  attach_config: boolean;
+  user_data: string;
+  instance_id?: string;
+  local_hostname?: string;
+  network_configuration?: string;
+}
+
 export type VMBootType = "UEFI" | "UEFISecureBoot" | "Legacy";
 
 interface VMInfoInterface {
@@ -101,6 +109,7 @@ interface VMInfoInterface {
   networks: VMNetInterface[];
   tpm_module: boolean;
   oem_strings: string[];
+  cloud_init: VMCloudInit;
 }
 
 export class VMInfo implements VMInfoInterface {
@@ -120,6 +129,7 @@ export class VMInfo implements VMInfoInterface {
   networks: VMNetInterface[];
   tpm_module: boolean;
   oem_strings: string[];
+  cloud_init: VMCloudInit;
 
   constructor(int: VMInfoInterface) {
     this.name = int.name;
@@ -138,6 +148,7 @@ export class VMInfo implements VMInfoInterface {
     this.networks = int.networks;
     this.tpm_module = int.tpm_module;
     this.oem_strings = int.oem_strings;
+    this.cloud_init = int.cloud_init;
   }
 
   static NewEmpty(): VMInfo {
@@ -153,6 +164,7 @@ export class VMInfo implements VMInfoInterface {
       networks: [],
       tpm_module: true,
       oem_strings: [],
+      cloud_init: { attach_config: false, user_data: "" },
     });
   }
 
diff --git a/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx b/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx
index 5e9f6be..33ef9f7 100644
--- a/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx
+++ b/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx
@@ -60,6 +60,7 @@ export function TokenRightsEditor(p: {
               <TableCell align="center">Get XML definition</TableCell>
               <TableCell align="center">Get autostart</TableCell>
               <TableCell align="center">Set autostart</TableCell>
+              <TableCell align="center">Get CloudInit disk</TableCell>
               <TableCell align="center">Backup disk</TableCell>
             </TableRow>
           </TableHead>
@@ -84,6 +85,13 @@ export function TokenRightsEditor(p: {
                 {...p}
                 right={{ verb: "PUT", path: "/api/vm/*/autostart" }}
               />
+              <CellRight
+                {...p}
+                right={{
+                  verb: "GET",
+                  path: "/api/vm/*/cloud_init_disk",
+                }}
+              />
               <CellRight
                 {...p}
                 right={{ verb: "POST", path: "/api/vm/*/disk/*/backup" }}
@@ -123,7 +131,15 @@ export function TokenRightsEditor(p: {
                   {...p}
                   right={{ verb: "PUT", path: `/api/vm/${v.uuid}/autostart` }}
                   parent={{ verb: "PUT", path: "/api/vm/*/autostart" }}
-                />{" "}
+                />
+                <CellRight
+                  {...p}
+                  right={{
+                    verb: "GET",
+                    path: `/api/vm/${v.uuid}/cloud_init_disk`,
+                  }}
+                  parent={{ verb: "GET", path: "/api/vm/*/cloud_init_disk" }}
+                />
                 <CellRight
                   {...p}
                   right={{