This is an old revision of the document!
Table of Contents
EVE-OS / ZEDEDA Application Volumes
Covers: what a volume is, what has to be configured in the GUI vs Terraform, how to deploy an app with a volume, and how to grow a volume after deployment. Primary examples use an image-backed HDD drive; HDD_EMPTY is covered as a separate use case at the end.
This page stays at the controller/app-instance config layer — the fields you set in the GUI, Terraform, or zcli. For what actually happens on the storage backend underneath (qcow2 vs. zvol, pool RAID vs. per-app resize), see the companion page: EVE-OS Volume Provisioning: Pool Layer vs. Per-App Layer.
Sources: lf-edge/eve, zededa/zedcloud Terraform provider docs, ZEDEDA Help Center, and real working Terraform (confirmed against actual zedcloud_application / zedcloud_volume_instance resources, not just provider doc snippets).
1. What a volume is
A volume/drive is a storage object attached to an app. It backs one of:
- The app's rootfs / OS disk (built from a Content Tree / image) — drive type HDD
- A blank data disk for app data — drive type HDD_EMPTY
- A shared/persistent data volume that can outlive the app, created standalone and attached by reference
A volume can be:
- Implicit — an entry in the
imageslist inside the app'smanifestblock, defined inline, tied to that app - Explicit — a standalone
zedcloud_volume_instanceresource (or GUI: Library > Volume Instances > +), created independently, then attached to an app viavolumelabelreferencing the standalone volume'slabel
Important layering note: everything on this page — maxsize, preserve, ignorepurge, drvtype — is a controller-side config object. It says nothing about how the volume is physically stored. EVE's volumemgr agent on the node reads this config and decides how to realize it on whatever persist backend the node has (ext4 or ZFS). That translation step is a completely separate layer — see the companion page.
2. Key fields
Standalone Volume Instance (''zedcloud_volume_instance'')
Created independently of any app, targets an edge node or cluster directly.
| GUI field | TF field | Notes |
|---|---|---|
| Type | type | VOLUME_INSTANCE_TYPE_BLOCKSTORAGE confirmed (blank disk). Content Tree type presumably VOLUME_INSTANCE_TYPE_CONTENTTREE — not yet confirmed against a real example. |
| Access Mode | accessmode | VOLUME_INSTANCE_ACCESS_MODE_READWRITE confirmed. Read-only presumably VOLUME_INSTANCE_ACCESS_MODE_READONLY — not confirmed. |
| Max Storage Size | size_bytes | Integer bytes. |
| Edge Node | device_id | Single edge node target. |
| (cluster target) | edge_node_cluster { id = … } | Used instead of device_id for a cluster. |
| Encrypted | cleartext | Inverted — cleartext = false means encrypted. |
| Label | label | This is the string an app's images block references via volumelabel to attach this standalone volume. |
App-attached image/drive (''manifest.images'' block, inside ''zedcloud_application'')
This is the actual, confirmed schema — nested inside manifest { }, as a repeated images { } block, not a top-level drives block as earlier drafts of this page assumed.
| Field | Notes |
|---|---|
imagename | Set for an image-backed entry (references a zedcloud_image by name). Used for the app's own container/rootfs image. |
volumelabel | Set instead of imagename when this entry attaches a pre-created standalone zedcloud_volume_instance by its label. Mutually exclusive with imagename in the real examples seen. |
imageformat | CONTAINER for the app's own OCI image; QCOW2 for an attached persistent block-storage volume. |
drvtype | HDD confirmed for a persistent data disk. (HDD_EMPTY, CDROM, NET — see section 6 and drive-type discussion earlier in this doc's history; not all re-confirmed against a real example yet.) |
mountpath | Path inside the app, e.g. /data. For a container-type app (app_type = “APP_TYPE_CONTAINER”), setting mountpath causes EVE to automatically mount that volume's filesystem at that path inside the running container — no manual mount step needed in the guest, the same way a Docker -v host:/container bind mount works. The container just finds /data already populated and writable at start. This is distinct from a raw VM block device (see HDD_EMPTY below and the guest-side step in section 5), where the guest OS gets a bare block device and has to partition/format/mount it itself — mountpath auto-mounting is a container-app convenience, not something that applies the same way to a classic VM's raw disk. |
cleartext | Inverted, same convention as the standalone volume instance — false means encrypted. |
ignorepurge | Real, distinct field from preserve — both appear together in real examples (ignorepurge = true, preserve = true). Exact semantic split between the two is still not confirmed from proto-level source; treat as “there are two separate purge-related knobs here, not one,” rather than assuming they're synonyms. |
preserve | See above — set alongside ignorepurge, not instead of it. |
maxsize | Bare integer, bytes — e.g. maxsize = 1073741824 for 1 GiB. Not a quoted string (correcting an earlier draft of this page). |
target | Seen as target = “Disk” on a persistent-volume image entry. Other possible values (e.g. for kernel/initrd-style entries) not confirmed. |
App-level version field
user_defined_version(onzedcloud_application) — a real example carries the comment *“bump to force a new app version after adding the :1080→:80 portmap ACL”*. This strongly implies this is the mechanism that pushes a manifest change (including animagesblock edit like amaxsizebump) out to the running app — bump the version string, apply, and the controller treats it as a new app version to roll out. This replaces an earlier, unconfirmed guess on this page about anallow_storage_resizeflag gating resize — that field does not appear in any real example provided and should be treated as unverified/likely incorrect unless you find it in an actual working config.
3. What's required to configure — GUI vs Terraform
GUI
- Standalone volume instances: Library > Volume Instances > + (Add Volume Instance). Set Type (Content Tree or Block Storage), Access Mode, target Edge Node, and either Image (Content Tree) or Max Storage Size (Block Storage).
- Implicit volumes are created inline while adding a drive to an app instance instead — no separate object.
- On the app's Drives pane (GUI's view into the
manifest.imageslist), define per drive: Drive Type, Image or Volume Label, Mount Path, Max Size, Preserve. - Editing Drives on an existing app instance triggers a Purge & Update in the GUI — the app purges and comes back online. Data on that drive is wiped unless Preserve was set.
Terraform (zededa/zedcloud provider)
- Standalone volume:
zedcloud_volume_instance— see field table above. - App + attached volume:
zedcloud_application, with amanifest { images { … } }block per drive. See section 4 for a full real example. - To attach a standalone volume instance to an app, reference its
labelviavolumelabelin one of the app'simagesentries — do not redeclare size/type there, those live on the volume instance itself. - Bump
user_defined_versionon thezedcloud_applicationresource to push out a manifest change.
ZCLI
- Explicit volume:
zcli volume-instance create <name> –volume-type=<type> –project=<project> (–edge-node=<node> | –edge-node-cluster=<cluster>) –size=<size> –access-mode=<mode> - Update:
zcli volume-instance update <name> [–title=…] [–description=…]
4. Step-by-step: Deploy an app with a persistent volume
This is the real, confirmed pattern: a standalone zedcloud_volume_instance (persistent, survives app redeploys) attached to an app's second images entry by volumelabel, alongside the app's own container image as the first entry.
Via Terraform
# 1. Standalone persistent volume, targeting a specific edge node
resource "zedcloud_volume_instance" "tf_demo_vol1_cont_persist" {
device_id = zedcloud_edgenode.demo_en_onlogic_cl250.id # Edge Node ID
accessmode = "VOLUME_INSTANCE_ACCESS_MODE_READWRITE"
cleartext = false
label = "adv-vol1-cont-persist"
name = "adv-vol1-cont-persist"
size_bytes = 1073741824 # 1 GiB
title = "adv-vol1-persist"
type = "VOLUME_INSTANCE_TYPE_BLOCKSTORAGE"
lifecycle {
ignore_changes = [device_id]
}
}
# 2. App that attaches the volume above as its second drive
resource "zedcloud_application" "tf_nginx_app_1" {
name = "TF-STND-NGINX-APP-1"
title = "TF-STND-NGINX-APP-1"
networks = 1
manifest {
ac_kind = "VMManifest"
ac_version = "1.2.0"
name = "TF-NGINX-APP-1"
owner {
user = "Manny"
company = "Zededa"
website = "www.zededa.com"
email = "manny@zededa.com"
}
desc {
app_category = "APP_CATEGORY_CLOUD_APPLICATION"
category = "APP_CATEGORY_UNSPECIFIED"
logo = {
url = "https://nginx.org/img/nginx_logo.svg"
}
}
images {
# first drive: the app's own container image
imagename = zedcloud_image.demo_nginx_alpine.name
cleartext = false
ignorepurge = true
imageformat = "CONTAINER"
}
images {
# second drive: attach the standalone persistent volume from above
volumelabel = zedcloud_volume_instance.tf_demo_vol1_cont_persist.label
imageformat = "QCOW2"
mountpath = "/data"
cleartext = false
drvtype = "HDD"
ignorepurge = true
preserve = true
maxsize = 1073741824
target = "Disk"
}
interfaces {
name = "eth0"
type = "ip"
directattach = false
privateip = false
acls {
matches {
type = "ip"
value = "0.0.0.0/0"
}
}
}
vmmode = "HV_PV"
enablevnc = true
resources {
name = "resourceType"
value = "custom"
}
resources {
name = "cpus"
value = 2
}
resources {
name = "memory"
value = 2097152
}
configuration {
custom_config {
add = true
name = "cloud-config"
override = true
template = ""
}
}
app_type = "APP_TYPE_CONTAINER"
deployment_type = "DEPLOYMENT_TYPE_STAND_ALONE"
cpu_pinning_enabled = false
}
user_defined_version = "1"
origin_type = "ORIGIN_LOCAL"
project_access_list = []
}
terraform plan/terraform apply.- Confirm in the GUI that the app instance reaches
RUNNINGand the second drive shows the persistent volume attached at/data.
5. Step-by-step: Grow a volume
Important: this is not a live/hot resize. Applying a manifest change that alters an images entry triggers a purge/redeploy of the app. Data on that drive is lost unless preserve/ignorepurge are set appropriately on that entry.
Via Terraform
- Raise
maxsizeon the relevantimagesblock (orsize_byteson the standalonezedcloud_volume_instance, if the volume is standalone rather than inline). - Bump
user_defined_versionon thezedcloud_applicationresource — this is the confirmed mechanism (see the real example's own comment: *“bump to force a new app version”*) for pushing the manifest change out. terraform apply.- Confirm the app redeploys and the drive reflects the new size.
images {
volumelabel = zedcloud_volume_instance.tf_demo_vol1_cont_persist.label
imageformat = "QCOW2"
mountpath = "/data"
cleartext = false
drvtype = "HDD"
ignorepurge = true
preserve = true
maxsize = 2147483648 # was 1 GiB, now 2 GiB
target = "Disk"
}
# ...
user_defined_version = "2" # bumped from "1" to push this change out
If the volume is instead a standalone zedcloud_volume_instance (not inline in the app's images list), raise size_bytes on that resource directly and apply — no app-side version bump needed in that case, since the volume object is independent of the app manifest.
Guest-side step
Raising the size enlarges the backing storage object (whatever volumemgr made it — see companion page); it does not automatically grow the filesystem living on top of it. What “growing the filesystem” requires depends on how the drive is presented:
- Raw VM block device (e.g. an
HDD_EMPTYdisk on a classic VM app): the guest sees a bare block device and must run the usual in-guest resize itself:- Linux ext4:
growpart /dev/xxx Nthenresize2fs /dev/xxxN - Linux xfs:
growpartthenxfs_growfs <mountpoint> - Windows: Disk Management > right-click volume > Extend Volume
- Container-mounted volume (
mountpathon a container-type app, like the/dataexamples in section 4): since EVE auto-mounts the volume's filesystem into the container rather than exposing a raw block device, whether the underlying filesystem auto-expands to fill the newmaxsize, or needs an equivalent resize step triggered by EVE/containerd on the next purge/redeploy, is not confirmed. Treat as an open question — verify on a test app instance by growingmaxsize, redeploying, and checking available space at/datafrom inside the container before assuming it “just works.”
6. Other use case: HDD_EMPTY (blank data disk, no attached content)
HDD_EMPTY is the drive-type equivalent of a standalone VOLUME_INSTANCE_TYPE_BLOCKSTORAGE volume, but declared inline in an app's images block instead of as a standalone object — no image, no content tree, no volumelabel reference, just a blank disk sized by maxsize.
- No
imagenameand novolumelabel— the entry is purelydrvtype = “HDD_EMPTY”plusmaxsize. - Arrives to the guest as a raw, unformatted block device; the guest OS (or first-boot cloud-init) has to partition and format it.
- Use this over a standalone volume instance when the data disk is specific to one app and doesn't need to be managed as its own independent object (e.g. reused across app versions, or attached to a different app later).
7. Open questions / to verify before relying on this in production
- Exact semantic split between
ignorepurgeandpreserve— both are confirmed real, distinct fields that appear together in working examples, but the precise difference in behavior between them is not confirmed from proto-level source. - Whether
allow_storage_resizeis a real field at all — it does not appear in any real example seen so far. Earlier drafts of this page asserted it existed based on generic provider-doc search snippets; treat that as unverified/likely wrong unless confirmed against an actual working resource. - Full set of valid values for
targeton animagesblock — only“Disk”confirmed. - Full set of valid
type/accessmodeenum values onzedcloud_volume_instancebeyondVOLUME_INSTANCE_TYPE_BLOCKSTORAGEandVOLUME_INSTANCE_ACCESS_MODE_READWRITE. - Whether any current EVE-OS version auto-grows the guest filesystem on boot after a resize (e.g., via cloud-init growpart module) — not confirmed; assume manual step is required unless verified for your specific image.
- How
maxsize/size_bytesactually gets realized on disk (qcow2 vs. per-app zvol, thin vs. thick) — different layer than anything on this page. See EVE-OS Volume Provisioning: Pool Layer vs. Per-App Layer. - This page is entirely about app-attached volumes. It does not cover the underlying
/persiststorage pool (ext4 disk or ZFS pool, RAID level) — that is an install-time/reinstall decision made via grub.cfg, not a controller-driven one. Covered on the EVE-OS Partitioning / ZFS wiki page.
