Table of Contents
Edge Apps
An Edge App in ZEDEDA Cloud is a bundle definition – the template that describes a workload: what image to run, how much compute to allocate, how many network interfaces to wire up, what ACL rules to apply, and how to pass initial configuration. The bundle is not deployed itself – you create App Instances from the bundle and target them at specific edge nodes.
Two objects, two steps:
zedcloud_application– the bundle definition (what to run)zedcloud_application_instance– the deployment (where to run it)
App Types
| app_type | Description |
|---|---|
APP_TYPE_VM | Virtual machine running under KVM/QEMU on EVE-KVM. Full hardware virtualization. |
APP_TYPE_CONTAINER | OCI container running under containerd. Lightweight, faster startup. |
APP_TYPE_COMPOSE | Docker Compose runtime. Runs a Compose file defining multiple containers. |
Deployment Types
| deployment_type | Description |
|---|---|
DEPLOYMENT_TYPE_STAND_ALONE | Single-node deployment. App instance runs on one specific edge node. Most common. |
DEPLOYMENT_TYPE_CLUSTER | Cluster-aware deployment. For EVE-k clusters – ZEDEDA schedules across cluster nodes. |
VM Modes (vmmode)
| vmmode | Description |
|---|---|
HV_HVM | Hardware Virtual Machine. Guest OS runs unmodified using VT-x hardware virtualization. Standard KVM mode. Use this for Linux VMs, Windows, and appliances like FortiGate and MikroTik. |
HV_HVM_LEGACY | HVM with emulated legacy IO drivers. For older guest OSes that need legacy device emulation. |
HV_PVH | ParaVirtualization Hardware (beta). Guest uses paravirt drivers. Primarily Xen mode for Windows. |
HV_FML | Full Machine Virtualization. Used when attaching NVIDIA GPUs via passthrough. Requires UEFI boot. |
Manifest Structure
Every VM app bundle in ZEDEDA is defined by a manifest. The manifest is the authoritative description of the workload. In Terraform, this is the manifest {} block inside zedcloud_application. The key sections are:
images– what image to use, format, drive type, encryption, sizeinterfaces– network interfaces with ACL rules and port mappingsresources– CPU, memory, storage allocationconfiguration– cloud-init / custom config templatevmmode,app_type,deployment_type– workload and deployment behavior
Key Manifest Fields
images block
| Field | Description |
|---|---|
imagename | Name of the registered image resource |
imageid | ID of the registered image resource |
imageformat | QCOW2, RAW, VMDK, CONTAINER |
cleartext | false = encrypt the volume at rest. Always false in production. |
drvtype | HDD = virtual hard disk. CDROM = read-only ISO. |
ignorepurge | true = this drive is NOT purged on Purge & Update. Use for the OS disk on appliances where you want to preserve config across image updates. false = drive is wiped on purge. |
maxsize | Maximum disk size in KB allocated for this drive. |
target | Disk = primary VM disk. Kernel = kernel image. |
resources block
Resources are defined as name/value pairs:
| name | value | Notes |
|---|---|---|
resourceType | custom | Always custom for explicit resource definition |
cpus | integer | Number of vCPUs assigned to the VM |
memory | integer (KB) | RAM in kilobytes. 2097152 = 2 GB. 4097152 = ~4 GB. |
storage | integer (KB) | Must match maxsize in the images block |
Memory is in kilobytes: 1 GB = 1048576 KB, 2 GB = 2097152 KB, 4 GB = 4194304 KB.
configuration block
configuration {
custom_config {
add = true
name = "cloud-config"
override = true
template = ""
}
}
add = trueenables custom configuration for this app bundletemplate = “”means no default template is baked in – the cloud-init config is provided at the app instance level, not the bundle leveloverride = truemeans the instance-level config overrides any bundle-level template
This pattern (empty template at bundle level, config at instance level) is the standard approach for reusable bundles deployed with per-node customization.
Terraform Examples
FortiGate Firewall VM (3 NICs, Port Mapping, ACLs)
A FortiGate 7.4.3 firewall appliance with 3 network interfaces. eth0 is the management/WAN interface with port mapping rules to expose SSH (10022 to 22) and HTTPS (10443 to 443) on the edge node. eth1 and eth2 are LAN/DMZ interfaces.
resource "zedcloud_application" "tf_atl_fw_app_1" {
name = "TF-FW-ATL-7.4.3"
title = "TF-FW-ATL-7.4.3"
networks = 3
manifest {
ac_kind = "VMManifest"
ac_version = "1.2.0"
name = "TF-FW-ATL-7.4.3"
owner {
user = "Your Name"
company = "Zededa"
website = "www.zededa.com"
email = "your@email.com"
}
desc {
app_category = "APP_CATEGORY_UNSPECIFIED"
category = "APP_CATEGORY_SECURITY"
logo = {
url = "https://www.fortinet.com/content/dam/fortinet/images/general/fortinet-logo.svg"
}
}
images {
imagename = zedcloud_image.tf_demo_fw_atl_image.name
imageid = zedcloud_image.tf_demo_fw_atl_image.id
imageformat = "QCOW2"
cleartext = false
drvtype = "HDD"
ignorepurge = true
maxsize = 40971520
target = "Disk"
}
interfaces {
name = "eth0"
directattach = false
privateip = false
acls {
matches { ### Outbound - allow all
type = "ip"
value = "0.0.0.0/0"
}
}
acls {
matches { ### Port map: external 10022 -> internal 22 (SSH)
type = "ip"
value = "0.0.0.0/0"
}
actions {
portmap = true
portmapto {
app_port = 22
}
}
matches {
type = "protocol"
value = "tcp"
}
matches {
type = "lport"
value = 10022
}
}
acls {
matches { ### Port map: external 10443 -> internal 443 (HTTPS)
type = "ip"
value = "0.0.0.0/0"
}
actions {
portmap = true
portmapto {
app_port = 443
}
}
matches {
type = "protocol"
value = "tcp"
}
matches {
type = "lport"
value = 10443
}
}
}
interfaces {
name = "eth1"
directattach = false
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
interfaces {
name = "eth2"
directattach = false
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
vmmode = "HV_HVM"
enablevnc = true
resources {
name = "resourceType"
value = "custom"
}
resources {
name = "cpus"
value = 2
}
resources {
name = "memory"
value = 4000000
}
resources {
name = "storage"
value = 40971520
}
configuration {
custom_config {
add = true
name = "cloud-config"
override = true
template = ""
}
}
app_type = "APP_TYPE_VM"
deployment_type = "DEPLOYMENT_TYPE_STAND_ALONE"
cpu_pinning_enabled = false
}
user_defined_version = "7.4.3"
origin_type = "ORIGIN_LOCAL"
}
Key points for this firewall app:
networks = 3– must match the number ofinterfacesblocks in the manifestignorepurge = trueon the OS drive – FortiGate stores its config inside the QCOW2 image. Setting ignorepurge preserves the drive (and its config) across image version updates. If ignorepurge were false, Purge & Update would wipe the firewall config.maxsize = 40971520KB = ~40 GB virtual disk- Port mapping ACLs: each portmap rule needs a matching block for protocol (tcp), the listening port on the edge node (
lport), the destination inside the app (app_port), and an IP match for allowed sources user_defined_versiontracks your app version string – visible in ZEDUI and useful for fleet managementorigin_type = “ORIGIN_LOCAL”means this app was created in your enterprise (not imported from the marketplace)
Ubuntu 24.04 VM (Simple, 1 NIC)
A simple Ubuntu cloud VM with a single outbound-only interface and cloud-init support:
resource "zedcloud_application" "tf_atl_ub_app_1" {
name = "TF-ATL-CL-UB-1"
title = "TF-ATL-CL-UB-1"
networks = 1
manifest {
ac_kind = "VMManifest"
ac_version = "1.2.0"
name = "TF-ATL-CL-UBUNTU-1"
owner {
user = "Your Name"
company = "Zededa"
website = "www.zededa.com"
email = "your@email.com"
}
desc {
app_category = "APP_CATEGORY_UNSPECIFIED"
category = "APP_CATEGORY_OPERATING_SYSTEM"
logo = {
url = "https://assets.ubuntu.com/v1/ff6a9a38-ubuntu-logo-2022.svg"
}
}
images {
imagename = zedcloud_image.demo_atl_ub_image_1.name
imageid = zedcloud_image.demo_atl_ub_image_1.id
imageformat = "QCOW2"
cleartext = false
drvtype = "HDD"
ignorepurge = false
maxsize = 20971520
target = "Disk"
}
interfaces {
name = "enp1s0"
directattach = false
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
vmmode = "HV_HVM"
enablevnc = true
resources {
name = "resourceType"
value = "custom"
}
resources {
name = "cpus"
value = 2
}
resources {
name = "memory"
value = 2097152
}
resources {
name = "storage"
value = 20971520
}
configuration {
custom_config {
add = true
name = "cloud-config"
override = true
template = ""
}
}
app_type = "APP_TYPE_VM"
deployment_type = "DEPLOYMENT_TYPE_STAND_ALONE"
cpu_pinning_enabled = false
}
user_defined_version = "24"
origin_type = "ORIGIN_LOCAL"
}
Key differences from the firewall:
ignorepurge = false– Ubuntu VM OS drive is wiped on Purge & Update. This is correct for a generic VM where you deliver per-instance config via cloud-init at deploy time, not stored inside the image.memory = 2097152KB = exactly 2 GB- Interface name
enp1s0– the PCI bus enumeration name inside the Ubuntu guest. This must match what the guest OS sees. For VirtIO-based images this is typicallyenp1s0,enp2s0, etc. For legacy emulation it may beeth0.
MikroTik Router VM (2 NICs)
A MikroTik CHR appliance with 2 NICs – WAN and LAN:
resource "zedcloud_application" "tf_demo_atl_mik_1" {
name = "TF-MIKROTIC-ATL-1"
title = "TF-MIKROTIC-ATL-1"
networks = 2
manifest {
ac_kind = "VMManifest"
ac_version = "1.2.0"
name = "TF-MIKROTIC-ATL-1"
desc {
app_category = "APP_CATEGORY_UNSPECIFIED"
category = "APP_CATEGORY_OPERATING_SYSTEM"
logo = {
url = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/MikroTik_Logo_%282022%29.svg/250px-MikroTik_Logo_%282022%29.svg.png"
}
}
images {
imagename = zedcloud_image.demo_mikrotick_1.name
imageid = zedcloud_image.demo_mikrotick_1.id
imageformat = "QCOW2"
cleartext = false
drvtype = "HDD"
ignorepurge = false
maxsize = 20971520
target = "Disk"
}
interfaces {
name = "eth0"
directattach = false
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
interfaces {
name = "eth1"
directattach = false
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
vmmode = "HV_HVM"
enablevnc = true
resources {
name = "resourceType"
value = "custom"
}
resources { name = "cpus" value = 2 }
resources { name = "memory" value = 4097152 }
resources { name = "storage" value = 20971520 }
configuration {
custom_config {
add = true
name = "cloud-config"
override = true
template = ""
}
}
app_type = "APP_TYPE_VM"
deployment_type = "DEPLOYMENT_TYPE_STAND_ALONE"
cpu_pinning_enabled = false
}
user_defined_version = "24"
origin_type = "ORIGIN_LOCAL"
}
Ubuntu VM with SR-IOV Direct Attach NICs
Ubuntu VM with one virtual NIC (for management) and two SR-IOV Virtual Function NICs passed directly to the guest. Used for high-throughput data plane workloads where the VM needs direct hardware access to a NIC, bypassing the EVE-OS virtual switch.
resource "zedcloud_application" "tf_demo_atl_srio_ub_local_ni_1" {
name = "TF-DEMO-SRIO-ATL-UB-LOCAL-NI-1"
title = "TF-DEMO-SRIO-ATL-UB-LOCAL-NI-1"
networks = 3
manifest {
ac_kind = "VMManifest"
ac_version = "1.2.0"
name = "TF-DEMO-SRIO-UBUNTU-APP"
images {
imagename = zedcloud_image.demo_atl_ub_image_1.name
imageid = zedcloud_image.demo_atl_ub_image_1.id
imageformat = "QCOW2"
cleartext = false
drvtype = "HDD"
ignorepurge = false
maxsize = 20971520
target = "Disk"
}
interfaces {
name = "enp1s0" ### Virtual NIC via EVE switch
directattach = false
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
interfaces {
name = "enp6s0" ### SR-IOV VF -- direct hardware passthrough
type = "IO_TYPE_ETH_VF"
directattach = true
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
interfaces {
name = "enp7s0" ### SR-IOV VF -- direct hardware passthrough
type = "IO_TYPE_ETH_VF"
directattach = true
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
vmmode = "HV_HVM"
enablevnc = true
resources { name = "resourceType" value = "custom" }
resources { name = "cpus" value = 2 }
resources { name = "memory" value = 4097152 }
resources { name = "storage" value = 20971520 }
configuration {
custom_config {
add = true
name = "cloud-config"
override = true
template = ""
}
}
app_type = "APP_TYPE_VM"
deployment_type = "DEPLOYMENT_TYPE_STAND_ALONE"
cpu_pinning_enabled = false
}
user_defined_version = "24"
origin_type = "ORIGIN_LOCAL"
}
SR-IOV key points:
type = “IO_TYPE_ETH_VF”– designates a SR-IOV Virtual Function interfacedirectattach = true– the VF is passed directly to the guest via VFIO/IOMMU, bypassing the EVE-OS virtual switch entirelydirectattach = falseon enp1s0 – this NIC goes through the EVE-OS network instance (virtual switch), providing management connectivity- The edge node hardware must support SR-IOV on that NIC, and the VF must be configured in EVE-OS before the app can use it
- ACL rules on directattach interfaces are advisory only – with direct passthrough, EVE-OS does not enforce ACLs on the data path (the VM has raw NIC access)
ACL and Port Mapping Reference
ACL rules control inbound and outbound traffic through EVE-OS's distributed firewall for virtual NIC interfaces (directattach = false only).
Outbound Allow-All (most common)
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
Allows the app to initiate outbound connections to any destination.
Inbound Port Mapping
Maps a port on the edge node's external IP to a port inside the app. Pattern: one ACL block with an actions { portmap } and matching blocks for protocol, external port (lport), and source IP:
acls {
actions {
portmap = true
portmapto {
app_port = 22 # port inside the app
}
}
matches {
type = "protocol"
value = "tcp"
}
matches {
type = "lport"
value = 10022 # port on the edge node
}
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
After deployment, connecting to edge-node-ip:10022 forwards to port 22 inside the app.
ACL Match Types
| type | value example | Description |
|---|---|---|
ip | 0.0.0.0/0 | Source or destination IP/CIDR |
protocol | tcp, udp, icmp | IP protocol |
lport | 10022 | Listening port on the edge node (external port for inbound rules) |
fport | 443 | Far/destination port for outbound rules |
App Categories
| category | Description |
|---|---|
APP_CATEGORY_SECURITY | Firewalls, VPN gateways, IDS/IPS |
APP_CATEGORY_OPERATING_SYSTEM | General purpose OS (Ubuntu, RHEL, Windows) |
APP_CATEGORY_NETWORK | Routers, switches, SD-WAN (MikroTik, etc.) |
APP_CATEGORY_UNSPECIFIED | No specific category |
ZEDUI: Creating an App Bundle
The ZEDUI can create app bundles from scratch for VMs. Terraform is recommended for repeatable deployments.
- Navigate to Marketplace > Edge Apps
- Click + in the upper right
- Select Virtual Machine as the app type
- Fill in Identity: Name, Title, Version, Category, Logo URL
- Configure Drives: select image, set format, size, encryption, and purge behavior
- Configure Resources: vCPU, RAM, storage
- Configure Interfaces: add NICs, set direct attach or virtual, define ACL rules
- Configure Custom Config: enable cloud-init if needed
- Click Save
Note: the only way to create a VM edge app from scratch is through the GUI. After creating it, you can export the manifest via ZCLI, modify it, and re-import it. Terraform can manage apps created this way by importing the resource.
App Instances
The app bundle defines what to run. An app instance defines where to run it. After creating the bundle, deploy instances to specific nodes:
resource "zedcloud_application_instance" "fw_instance_atl_01" {
name = "FW-ATL-NODE-01"
title = "FortiGate ATL Node 01"
app_id = zedcloud_application.tf_atl_fw_app_1.id
device_id = zedcloud_edgenode.demo_en_advantech_1.id
project_id = zedcloud_project.demo_project.id
activate = true
interfaces {
name = "eth0"
netinstid = zedcloud_network_instance.wan_ni.id
}
interfaces {
name = "eth1"
netinstid = zedcloud_network_instance.lan_ni.id
}
interfaces {
name = "eth2"
netinstid = zedcloud_network_instance.dmz_ni.id
}
}
The instance-level interfaces block binds each interface name from the bundle to an actual network instance on the target node.
What Happens on the Edge Node
- The controller delivers the app instance config to EVE-OS at next heartbeat
- EVE-OS transitions the instance through: INIT > DOWNLOAD > INSTALL > RUNNING
- DOWNLOAD: EVE-OS fetches the QCOW2 image from the datastore, verifies SHA256
- INSTALL: EVE-OS creates the KVM domain definition – allocates vCPUs, RAM, creates the virtual disk from the image. For
ignorepurge = truedrives, an existing volume is reattached rather than recreated. - Network wiring: EVE-OS creates TAP interfaces and connects them to the network instance bridges. For SR-IOV (
directattach = true), EVE-OS binds the VF to VFIO and passes it to the KVM domain. - Port mapping ACLs: EVE-OS programs iptables/nftables rules on the host to forward
lporttraffic to the app's internal IP andapp_port - RUNNING: QEMU starts the VM. EVE-OS reports the run state to the controller at each heartbeat.
- cloud-init: if a custom config was provided at the instance level, EVE-OS presents it as a cloud-init datasource (NoCloud) to the guest on first boot via a virtual CD-ROM drive
Memory Quick Reference
| GB | KB (for memory field) |
|---|---|
| 1 GB | 1048576 |
| 2 GB | 2097152 |
| 4 GB | 4194304 |
| 8 GB | 8388608 |
| 16 GB | 16777216 |
