{"id":2412,"date":"2026-06-02T10:52:09","date_gmt":"2026-06-02T14:52:09","guid":{"rendered":"https:\/\/enterpriseadmins.org\/blog\/?p=2412"},"modified":"2026-06-02T10:52:09","modified_gmt":"2026-06-02T14:52:09","slug":"building-a-tinycore-linux-ova-with-custom-ovf-properties","status":"publish","type":"post","link":"https:\/\/enterpriseadmins.org\/blog\/scripting\/building-a-tinycore-linux-ova-with-custom-ovf-properties\/","title":{"rendered":"Building a TinyCore Linux OVA with Custom OVF Properties"},"content":{"rendered":"\n<p>I&#8217;ve recently been interested in creating a TinyCore Linux virtual appliance.  This OVA would allow for some customization, like hostname, IP address, default gateway, and DNS settings.<\/p>\n\n\n\n<p>I&#8217;ve posted about TinyCore Linux before, most recently 2 years ago: <a href=\"https:\/\/enterpriseadmins.org\/blog\/scripting\/tinycore-15-virtual-machine-very-small-vm-for-testing\/\">https:\/\/enterpriseadmins.org\/blog\/scripting\/tinycore-15-virtual-machine-very-small-vm-for-testing\/<\/a>.  I really enjoy this very lightweight VM as it works perfectly for demos.  In a very small (~28MB in this example) package, we can have a running virtual machine with VMware Tools. <\/p>\n\n\n\n<p>I&#8217;ve recently started storing virtual machines that I need to deploy, like the Nested ESXi Fling, as OVAs in a content library.  These OVAs are typically customized with OVF properties.  I&#8217;ve never created my own OVA with custom properties, but found a great guide here: <a href=\"https:\/\/williamlam.com\/2019\/02\/building-your-own-virtual-appliances-using-ovf-properties-part-1.html\">https:\/\/williamlam.com\/2019\/02\/building-your-own-virtual-appliances-using-ovf-properties-part-1.html<\/a>.<\/p>\n\n\n\n<p>I wanted to take these steps and apply them to a TinyCore Linux appliance.<\/p>\n\n\n\n<p>At a high level, this appliance works by exposing VMware OVF properties as <code>guestinfo.*<\/code> values.  During boot, a startup script running inside TinyCore Linux reads those values through VMware Tools and applies the requested network and hostname configuration automatically.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating Virtual Machine<\/h2>\n\n\n\n<p>I started by creating a minimal virtual machine, with hardware compatibility going back to ESXi 7.0u2 and later (vmx-19), Other 5.x or later Linux (32-bit), 1vCPU, 1GB RAM, and configured the VM to boot using BIOS instead of EFI.  I typically choose BIOS as I&#8217;ve had issues with VMs booting from the CD for the initial install with EFI.<\/p>\n\n\n\n<p>In the VM, I installed TinyCore command line only to the entire disk.  I then installed a few dependencies, copied a script from a webserver, and set that script to run when the system boots (by appending to the builtin <code>\/opt\/bootlocal.sh<\/code> script).  Note: VMware Tools specifically are required for the mechanisms later in this post to function.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tce-load -wi curl pcre open-vm-tools\nsudo wget http:\/\/www.example.com\/build\/tc-ova.txt -O \/opt\/tc-ova.sh\nsudo chmod +x \/opt\/tc-ova.sh\necho \"\/opt\/tc-ova.sh &gt; \/tmp\/tc-ova-boot.log 2&gt;&amp;1\" | sudo tee -a \/opt\/bootlocal.sh\necho y | backup<\/code><\/pre>\n\n\n\n<p>The <code>tc-ova.txt<\/code> file on my webserver (www.example.com) can be found on GitHub here: <a href=\"https:\/\/github.com\/bwuch\/code-snips\/blob\/master\/build\/tc-ova.txt\">https:\/\/github.com\/bwuch\/code-snips\/blob\/master\/build\/tc-ova.txt<\/a>. This file has a .txt extension, but that is so I don&#8217;t need to create a .sh mime type on my web server.  The file is a generic shell script that retrieves OVF properties using VMware Tools and the <code>vmtoolsd --cmd \"info-get guestinfo.*\"<\/code> interface.  It is renamed to have a .sh extension by the wget command.  The script allows the guest operating system to read values provided during deployment without requiring cloud-init or additional provisioning frameworks.  After reading those values, the script will apply them to set IP &amp; subnet mask, default gateway, DNS servers, and hostname, if any of those values are found as OVF properties. By keeping all OVF properties optional, the appliance remains flexible.  A deployment can use DHCP with minimal input, or fully specify static networking when needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating OVF Properties<\/h2>\n\n\n\n<p>The guide I used for initial setup (<a href=\"https:\/\/williamlam.com\/2019\/02\/building-your-own-virtual-appliances-using-ovf-properties-part-1.html.\">https:\/\/williamlam.com\/2019\/02\/building-your-own-virtual-appliances-using-ovf-properties-part-1.html<\/a>) shows how to create these OVF properties in the UI.  I needed to create a handful of properties, with specific names, and was interested in setting some default values.  While I could have done this in the UI, I decided to automate the creation of OVF properties with PowerCLI.  The script below documents my property names in a CSV file embedded into the script, loops through them to apply them to the VM, and finally exports the VM as an OVA.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$applianceVersion = 'TinyCore_17.0_Appliance'\n$vmName = 'h461-tinycore-01'\n\n$ovfProperties = @\"\nKey,Label,Type,Description,DefaultValue\nguestinfo.hostname,Hostname,string,Optional: Short hostname,\nguestinfo.domain,DNS Domain,string,Optional: Will be appended to Hostname to set FQDN.,lab.enterpriseadmins.org\nguestinfo.dns,DNS Server,string,Optional: Space or comma separated list of DNS servers,192.168.127.30 192.168.32.30\nguestinfo.ipaddress,IP Address,string,Optional: IPv4 address to assign to VM\nguestinfo.netmask,Netmask,string,\"Optional: IPv4 Netmask, please specify if IP Address has been set.\"\nguestinfo.gateway,Default Gateway,string,Optional: IPv4 default gateway\n\"@ | ConvertFrom-Csv\n\n\n$spec = New-Object VMware.Vim.VirtualMachineConfigSpec         # Main VM config spec\n$spec.vAppConfig = New-Object VMware.Vim.VmConfigSpec          # vApp config container\n\n$propertySpecs = @()\n\n$keyId = 0\nforeach ($prop in $ovfProperties) {\n\n    # Create property info object\n    $propertyInfo = New-Object VMware.Vim.VAppPropertyInfo\n    $propertyInfo.Key = $keyId\n    $propertyInfo.Id = $prop.Key\n    $propertyInfo.Category = \"Guestinfo\"\n    $propertyInfo.Label = $prop.Label\n    $propertyInfo.Type = $prop.Type\n    $propertyInfo.DefaultValue = $prop.DefaultValue\n    $propertyInfo.UserConfigurable = $true\n    $propertyInfo.Description = $prop.Description\n\n    # Create property spec wrapper\n    $propertySpec = New-Object VMware.Vim.VAppPropertySpec\n    $propertySpec.Operation = \"add\"\n    $propertySpec.Info = $propertyInfo\n\n    # Add to array\n    $propertySpecs += $propertySpec\n    $keyId++\n}\n\n# Attach property specs to vApp config\n$spec.vAppConfig.Property = $propertySpecs\n\n$spec.VAppConfig.Product = New-Object VMware.Vim.VAppProductSpec&#91;] (1)\n$spec.VAppConfig.Product&#91;0] = New-Object VMware.Vim.VAppProductSpec\n$spec.VAppConfig.Product&#91;0].Operation = 'add'\n$spec.VAppConfig.Product&#91;0].Info = New-Object VMware.Vim.VAppProductInfo\n$spec.VAppConfig.Product&#91;0].Info.VendorUrl = 'http:\/\/tinycorelinux.net'\n$spec.VAppConfig.Product&#91;0].Info.Vendor = 'TinyCoreLinux'\n$spec.VAppConfig.Product&#91;0].Info.Name = $applianceVersion\n$spec.VAppConfig.Product&#91;0].Info.ProductUrl = 'http:\/\/tinycorelinux.net'\n$spec.VAppConfig.Product&#91;0].Info.Key = -1\n$spec.VAppConfig.OvfEnvironmentTransport = New-Object String&#91;] (1)\n$spec.VAppConfig.OvfEnvironmentTransport&#91;0] = 'com.vmware.guestInfo'\n\n(Get-VM $vmName).ExtensionData.ReconfigVM_Task($spec)\nStart-Sleep -Seconds 5  # allow the previous task to complete, we could make this more robust by checking for actual completion of previous task.\nGet-VM $vmName | Export-VApp -Destination D:\\tmp -Name $applianceVersion -Format:Ova -Description $applianceVersion<\/code><\/pre>\n\n\n\n<p>The resulting OVA file was very small, approximately 28MB on disk.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the Deployment<\/h2>\n\n\n\n<p>When deploying this appliance through the UI, all properties have valid values by default, since all fields are optional.  I&#8217;ve confirmed that this works as expected and the VM gets its IP from DHCP and the host name is set to the default value (<code>box<\/code>). <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2026\/05\/image-6.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"704\" src=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2026\/05\/image-6-1024x704.png\" alt=\"\" class=\"wp-image-2419\" srcset=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2026\/05\/image-6-1024x704.png 1024w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2026\/05\/image-6-300x206.png 300w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2026\/05\/image-6-768x528.png 768w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2026\/05\/image-6.png 1144w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>For another test, I deployed the OVA using PowerCLI.  I&#8217;ll include that script below as well.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$file=\"D:\\tmp\\TinyCore_17.0_Appliance.ova\"\n$vmName=   'h461-tinycore-03'\n\n# Get OVF Config\n$ovfConfig = Get-OvfConfiguration -Ovf $file\n\n# Set OVF Properties\n$ovfConfig.NetworkMapping.dvportgroup_34861.Value = '192.168.10.0'\n$ovfConfig.common.guestinfo.hostname.value  = $vmName\n$ovfConfig.common.guestinfo.ipaddress.value = '192.168.10.222'\n$ovfConfig.common.guestinfo.netmask.value   = '255.255.255.0'\n$ovfConfig.common.guestinfo.gateway.value   = '192.168.10.1'\n\n$newVmSettings = @{\n  Source            = $file\n  OvfConfiguration  = $ovfConfig\n  Name              = $vmName\n  VMHost            = 'core-esxi-34.lab.enterpriseadmins.org'\n  Location          = '30-Greenfield'\n  Datastore         = (Get-Datastore core-tier1-nfs1)\n  InventoryLocation = 'Testing'\n  DiskStorageFormat = 'thin'\n  Confirm           = $false\n  Force             = $true\n}\n$newVM = Import-VApp @newVmSettings\n$newVM | Start-VM<\/code><\/pre>\n\n\n\n<p>You may notice that we did not set the <code>dns<\/code> or <code>domain<\/code> properties, as those already had default values.  After powering on the VM , we can confirm that the settings were updated and networking is functioning as expected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Changing the deployment<\/h2>\n\n\n\n<p>Once our settings have been set, we can browse to the VM &gt; Configure &gt; vApp Options tab (while the VM is powered off) and adjust our values with the <code>SET VALUE<\/code> button.  When the virtual machine is powered on, the script will automatically run at startup, read the updated OVF properties, and set the values as desired.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>I originally started this project because I wanted an extremely small appliance that could be stored in a Content Library and deployed quickly whenever I needed a Linux VM for testing or demos. The result is a TinyCore Linux appliance that occupies only about 28MB on disk while still supporting deployment-time customization through standard OVF properties.<\/p>\n\n\n\n<p>This approach has already proven useful in my lab, and I expect it will become my default &#8220;utility VM&#8221; going forward. The same techniques could easily be expanded to support additional configuration options or application-specific appliances, making TinyCore Linux a surprisingly capable foundation for custom VMware virtual appliances.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve recently been interested in creating a TinyCore Linux virtual appliance. This OVA would allow for some customization, like hostname, IP address, default gateway, and DNS settings. I&#8217;ve posted about TinyCore Linux before, most recently 2 years ago: https:\/\/enterpriseadmins.org\/blog\/scripting\/tinycore-15-virtual-machine-very-small-vm-for-testing\/. I &hellip; <a href=\"https:\/\/enterpriseadmins.org\/blog\/scripting\/building-a-tinycore-linux-ova-with-custom-ovf-properties\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[9,3,4],"tags":[],"class_list":["post-2412","post","type-post","status-publish","format-standard","hentry","category-lab-infrastructure","category-scripting","category-virtualization"],"_links":{"self":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts\/2412","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/comments?post=2412"}],"version-history":[{"count":4,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts\/2412\/revisions"}],"predecessor-version":[{"id":2422,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts\/2412\/revisions\/2422"}],"wp:attachment":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/media?parent=2412"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/categories?post=2412"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/tags?post=2412"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}