diff --git a/virtweb_backend/src/constants.rs b/virtweb_backend/src/constants.rs
index e69e82c..f1dfe65 100644
--- a/virtweb_backend/src/constants.rs
+++ b/virtweb_backend/src/constants.rs
@@ -122,19 +122,22 @@ pub const API_TOKEN_DESCRIPTION_MAX_LENGTH: usize = 30;
 pub const API_TOKEN_RIGHT_PATH_MAX_LENGTH: usize = 255;
 
 /// Qemu image program path
-pub const QEMU_IMAGE_PROGRAM: &str = "/usr/bin/qemu-img";
+pub const PROGRAM_QEMU_IMAGE: &str = "/usr/bin/qemu-img";
 
 /// IP program path
-pub const IP_PROGRAM: &str = "/usr/sbin/ip";
+pub const PROGRAM_IP: &str = "/usr/sbin/ip";
 
 /// Copy program path
-pub const COPY_PROGRAM: &str = "/bin/cp";
+pub const PROGRAM_COPY: &str = "/bin/cp";
 
 /// Gzip program path
-pub const GZIP_PROGRAM: &str = "/usr/bin/gzip";
+pub const PROGRAM_GZIP: &str = "/usr/bin/gzip";
 
 /// Bash program
-pub const BASH_PROGRAM: &str = "/usr/bin/bash";
+pub const PROGRAM_BASH: &str = "/usr/bin/bash";
 
 /// DD program
-pub const DD_PROGRAM: &str = "/usr/bin/dd";
+pub const PROGRAM_DD: &str = "/usr/bin/dd";
+
+/// cloud-localds program
+pub const PROGRAM_CLOUD_LOCALDS: &str = "/usr/bin/cloud-localds";
diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs
index 91a2a7d..c9b509d 100644
--- a/virtweb_backend/src/main.rs
+++ b/virtweb_backend/src/main.rs
@@ -47,13 +47,17 @@ async fn main() -> std::io::Result<()> {
 
     log::debug!("Checking for required programs");
     exec_utils::check_program(
-        constants::QEMU_IMAGE_PROGRAM,
+        constants::PROGRAM_QEMU_IMAGE,
         "QEMU disk image utility is required to manipulate QCow2 files!",
     );
     exec_utils::check_program(
-        constants::IP_PROGRAM,
+        constants::PROGRAM_IP,
         "ip is required to access bridges information!",
     );
+    exec_utils::check_program(
+        constants::PROGRAM_CLOUD_LOCALDS,
+        "cloud-localds from package cloud-image-utils is required to build cloud-init images!",
+    );
 
     log::debug!("Create required directory, if missing");
     files_utils::create_directory_if_missing(AppConfig::get().iso_storage_path()).unwrap();
diff --git a/virtweb_backend/src/utils/file_disks_utils.rs b/virtweb_backend/src/utils/file_disks_utils.rs
index 228f011..db1d40f 100644
--- a/virtweb_backend/src/utils/file_disks_utils.rs
+++ b/virtweb_backend/src/utils/file_disks_utils.rs
@@ -124,7 +124,7 @@ impl DiskFileInfo {
             }
 
             DiskFileFormat::QCow2 { virtual_size } => {
-                let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
                 cmd.arg("create")
                     .arg("-f")
                     .arg("qcow2")
@@ -161,7 +161,7 @@ impl DiskFileInfo {
         let mut cmd = match (self.format, dest_format) {
             // Decompress QCow2
             (DiskFileFormat::CompressedQCow2, DiskFileFormat::QCow2 { .. }) => {
-                let mut cmd = Command::new(constants::GZIP_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_GZIP);
                 cmd.arg("--keep")
                     .arg("--decompress")
                     .arg("--to-stdout")
@@ -172,7 +172,7 @@ impl DiskFileInfo {
 
             // Compress QCow2
             (DiskFileFormat::QCow2 { .. }, DiskFileFormat::CompressedQCow2) => {
-                let mut cmd = Command::new(constants::GZIP_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_GZIP);
                 cmd.arg("--keep")
                     .arg("--to-stdout")
                     .arg(&self.file_path)
@@ -182,7 +182,7 @@ impl DiskFileInfo {
 
             // Convert QCow2 to Raw file
             (DiskFileFormat::QCow2 { .. }, DiskFileFormat::Raw { is_sparse }) => {
-                let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
                 cmd.arg("convert")
                     .arg("-f")
                     .arg("qcow2")
@@ -201,7 +201,7 @@ impl DiskFileInfo {
             // Clone a QCow file, using qemu-image instead of cp might improve "sparsification" of
             // file
             (DiskFileFormat::QCow2 { .. }, DiskFileFormat::QCow2 { .. }) => {
-                let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
                 cmd.arg("convert")
                     .arg("-f")
                     .arg("qcow2")
@@ -214,7 +214,7 @@ impl DiskFileInfo {
 
             // Convert Raw to QCow2 file
             (DiskFileFormat::Raw { .. }, DiskFileFormat::QCow2 { .. }) => {
-                let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
                 cmd.arg("convert")
                     .arg("-f")
                     .arg("raw")
@@ -228,7 +228,7 @@ impl DiskFileInfo {
 
             // Render raw file non sparse
             (DiskFileFormat::Raw { is_sparse: true }, DiskFileFormat::Raw { is_sparse: false }) => {
-                let mut cmd = Command::new(constants::COPY_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_COPY);
                 cmd.arg("--sparse=never")
                     .arg(&self.file_path)
                     .arg(&temp_file);
@@ -237,7 +237,7 @@ impl DiskFileInfo {
 
             // Render raw file sparse
             (DiskFileFormat::Raw { is_sparse: false }, DiskFileFormat::Raw { is_sparse: true }) => {
-                let mut cmd = Command::new(constants::DD_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_DD);
                 cmd.arg("conv=sparse")
                     .arg(format!("if={}", self.file_path.display()))
                     .arg(format!("of={}", temp_file.display()));
@@ -246,7 +246,7 @@ impl DiskFileInfo {
 
             // Compress Raw
             (DiskFileFormat::Raw { .. }, DiskFileFormat::CompressedRaw) => {
-                let mut cmd = Command::new(constants::GZIP_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_GZIP);
                 cmd.arg("--keep")
                     .arg("--to-stdout")
                     .arg(&self.file_path)
@@ -256,7 +256,7 @@ impl DiskFileInfo {
 
             // Decompress Raw to not sparse file
             (DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: false }) => {
-                let mut cmd = Command::new(constants::GZIP_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_GZIP);
                 cmd.arg("--keep")
                     .arg("--decompress")
                     .arg("--to-stdout")
@@ -268,12 +268,12 @@ impl DiskFileInfo {
             // Decompress Raw to sparse file
             // https://benou.fr/www/ben/decompressing-sparse-files.html
             (DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: true }) => {
-                let mut cmd = Command::new(constants::BASH_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_BASH);
                 cmd.arg("-c").arg(format!(
                     "{} -d -c {} | {} conv=sparse of={}",
-                    constants::GZIP_PROGRAM,
+                    constants::PROGRAM_GZIP,
                     self.file_path.display(),
-                    constants::DD_PROGRAM,
+                    constants::PROGRAM_DD,
                     temp_file.display()
                 ));
                 cmd
@@ -281,7 +281,7 @@ impl DiskFileInfo {
 
             // Dumb copy of file
             (a, b) if a == b => {
-                let mut cmd = Command::new(constants::COPY_PROGRAM);
+                let mut cmd = Command::new(constants::PROGRAM_COPY);
                 cmd.arg("--sparse=auto")
                     .arg(&self.file_path)
                     .arg(&temp_file);
@@ -341,7 +341,7 @@ struct QCowInfoOutput {
 /// Get QCow2 virtual size
 fn qcow_virt_size(path: &Path) -> anyhow::Result<FileSize> {
     // Run qemu-img
-    let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
+    let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
     cmd.args([
         "info",
         path.to_str().unwrap_or(""),
@@ -353,7 +353,7 @@ fn qcow_virt_size(path: &Path) -> anyhow::Result<FileSize> {
     if !output.status.success() {
         anyhow::bail!(
             "{} info failed, status: {}, stderr: {}",
-            constants::QEMU_IMAGE_PROGRAM,
+            constants::PROGRAM_QEMU_IMAGE,
             output.status,
             String::from_utf8_lossy(&output.stderr)
         );
diff --git a/virtweb_backend/src/utils/net_utils.rs b/virtweb_backend/src/utils/net_utils.rs
index 8a55d04..d835718 100644
--- a/virtweb_backend/src/utils/net_utils.rs
+++ b/virtweb_backend/src/utils/net_utils.rs
@@ -145,13 +145,13 @@ struct IPBridgeInfo {
 
 /// Get the list of bridge interfaces
 pub fn bridges_list() -> anyhow::Result<Vec<String>> {
-    let mut cmd = Command::new(constants::IP_PROGRAM);
+    let mut cmd = Command::new(constants::PROGRAM_IP);
     cmd.args(["-json", "link", "show", "type", "bridge"]);
     let output = cmd.output()?;
     if !output.status.success() {
         anyhow::bail!(
             "{} failed, status: {}, stderr: {}",
-            constants::IP_PROGRAM,
+            constants::PROGRAM_IP,
             output.status,
             String::from_utf8_lossy(&output.stderr)
         );
diff --git a/virtweb_docs/SETUP_DEV.md b/virtweb_docs/SETUP_DEV.md
index b587d05..b337e18 100644
--- a/virtweb_docs/SETUP_DEV.md
+++ b/virtweb_docs/SETUP_DEV.md
@@ -5,9 +5,9 @@
 sudo apt install libvirt-dev
 ```
 
-2. Libvirt must also be installed:
+2. Libvirt and cloud image utilities must also be installed:
 ```bash
-sudo apt install qemu-kvm libvirt-daemon-system
+sudo apt install qemu-kvm libvirt-daemon-system cloud-image-utils
 ```
 
 3. Allow the current user to manage VMs:
diff --git a/virtweb_docs/SETUP_PROD.md b/virtweb_docs/SETUP_PROD.md
index 9e9709e..a72f2b5 100644
--- a/virtweb_docs/SETUP_PROD.md
+++ b/virtweb_docs/SETUP_PROD.md
@@ -12,10 +12,10 @@ The release file will be available in `virtweb_backend/target/release/virtweb_ba
 This is the only artifact that must be copied to the server. It is recommended to copy it to the `/usr/local/bin` directory.
 
 ## Install requirements
-In order to work properly, VirtWeb relies on `libvirt`, `qemu` and `kvm`:
+In order to work properly, VirtWeb relies on `libvirt`, `qemu`, `kvm` and `cloud-localds`:
 
 ```bash
-sudo apt install qemu-kvm libvirt-daemon-system libvirt0 libvirt-clients libvirt-daemon bridge-utils
+sudo apt install qemu-kvm libvirt-daemon-system libvirt0 libvirt-clients libvirt-daemon bridge-utils cloud-image-utils
 ```
 
 ## Dedicated user