@@ -420,14 +420,31 @@ func (m *manager) createInstance(
420420 // 15. Validate and attach volumes
421421 if len (req .Volumes ) > 0 {
422422 log .DebugContext (ctx , "validating volumes" , "instance_id" , id , "count" , len (req .Volumes ))
423- for _ , volAttach := range req .Volumes {
424- // Check volume exists
425- _ , err := m .volumeManager .GetVolume (ctx , volAttach .VolumeID )
423+ resolvedVolumes := make ([]VolumeAttachment , len (req .Volumes ))
424+ for i , volAttach := range req .Volumes {
425+ // Check volume exists and get its details
426+ vol , err := m .volumeManager .GetVolume (ctx , volAttach .VolumeID )
426427 if err != nil {
427428 log .ErrorContext (ctx , "volume not found" , "instance_id" , id , "volume_id" , volAttach .VolumeID , "error" , err )
428429 return nil , fmt .Errorf ("volume %s: %w" , volAttach .VolumeID , err )
429430 }
430431
432+ resolvedVolumes [i ] = volAttach
433+
434+ // Populate NFS config from the volume for RWX volumes
435+ if vol .NFS != nil {
436+ resolvedVolumes [i ].NFS = & VolumeNFSConfig {
437+ Server : vol .NFS .Server ,
438+ ExportPath : vol .NFS .ExportPath ,
439+ Version : vol .NFS .Version ,
440+ Options : vol .NFS .Options ,
441+ }
442+ // NFS volumes require networking for NFS client access
443+ if ! req .NetworkEnabled {
444+ return nil , fmt .Errorf ("volume %s: NFS volumes require network.enabled=true" , volAttach .VolumeID )
445+ }
446+ }
447+
431448 // Mark volume as attached (AttachVolume handles multi-attach validation)
432449 if err := m .volumeManager .AttachVolume (ctx , volAttach .VolumeID , volumes.AttachVolumeRequest {
433450 InstanceID : id ,
@@ -444,7 +461,7 @@ func (m *manager) createInstance(
444461 m .volumeManager .DetachVolume (ctx , volumeID , id )
445462 })
446463
447- // Create overlay disk for volumes with overlay enabled
464+ // Create overlay disk for volumes with overlay enabled (not for NFS)
448465 if volAttach .Overlay {
449466 log .DebugContext (ctx , "creating volume overlay disk" , "instance_id" , id , "volume_id" , volAttach .VolumeID , "size" , volAttach .OverlaySize )
450467 if err := m .createVolumeOverlayDisk (id , volAttach .VolumeID , volAttach .OverlaySize ); err != nil {
@@ -453,8 +470,12 @@ func (m *manager) createInstance(
453470 }
454471 }
455472 }
456- // Store volume attachments in metadata
457- stored .Volumes = req .Volumes
473+ // Re-validate with NFS info populated
474+ if err := validateVolumeAttachments (resolvedVolumes ); err != nil {
475+ return nil , fmt .Errorf ("%w: %v" , ErrInvalidRequest , err )
476+ }
477+ // Store resolved volume attachments in metadata
478+ stored .Volumes = resolvedVolumes
458479 }
459480
460481 // 16. Create config disk (needs Instance for buildVMConfig)
@@ -614,10 +635,13 @@ func validateCreateRequest(req *CreateInstanceRequest) error {
614635}
615636
616637// validateVolumeAttachments validates volume attachment requests
617- func validateVolumeAttachments (volumes []VolumeAttachment ) error {
618- // Count total devices needed (each overlay volume needs 2 devices: base + overlay )
638+ func validateVolumeAttachments (vols []VolumeAttachment ) error {
639+ // Count total block devices needed (NFS volumes don't consume a device )
619640 totalDevices := 0
620- for _ , vol := range volumes {
641+ for _ , vol := range vols {
642+ if vol .NFS != nil {
643+ continue // NFS volumes don't use block devices
644+ }
621645 totalDevices ++
622646 if vol .Overlay {
623647 totalDevices ++ // Overlay needs an additional device
@@ -628,7 +652,7 @@ func validateVolumeAttachments(volumes []VolumeAttachment) error {
628652 }
629653
630654 seenPaths := make (map [string ]bool )
631- for _ , vol := range volumes {
655+ for _ , vol := range vols {
632656 // Validate mount path is absolute
633657 if ! filepath .IsAbs (vol .MountPath ) {
634658 return fmt .Errorf ("volume %s: mount path %q must be absolute" , vol .VolumeID , vol .MountPath )
@@ -648,6 +672,11 @@ func validateVolumeAttachments(volumes []VolumeAttachment) error {
648672 }
649673 seenPaths [cleanPath ] = true
650674
675+ // NFS volumes cannot use overlay mode
676+ if vol .NFS != nil && vol .Overlay {
677+ return fmt .Errorf ("volume %s: overlay mode is not supported for NFS volumes" , vol .VolumeID )
678+ }
679+
651680 // Validate overlay mode requirements
652681 if vol .Overlay {
653682 if ! vol .Readonly {
@@ -765,8 +794,11 @@ func (m *manager) buildHypervisorConfig(ctx context.Context, inst *Instance, ima
765794 {Path : m .paths .InstanceConfigDisk (inst .Id ), Readonly : true , IOBps : ioBps , IOBurstBps : burstBps },
766795 }
767796
768- // Add attached volumes as additional disks
797+ // Add attached volumes as additional disks (skip NFS volumes — they're network-mounted)
769798 for _ , volAttach := range inst .Volumes {
799+ if volAttach .NFS != nil {
800+ continue // NFS volumes don't have a local block device
801+ }
770802 volumePath := m .volumeManager .GetVolumePath (volAttach .VolumeID )
771803 if volAttach .Overlay {
772804 // Base volume is always read-only when overlay is enabled
0 commit comments