Repository: hashicorp/packer-plugin-vsphere

Issue:
Add support for datastore cluster storage
_This issue was originally opened by @vheon as hashicorp/packer#10004. It was migrated here as a result of the [Packer plugin split](https://github.com/hashicorp/packer/issues/8610#issuecomment-770034737). The original body of the issue is below._

<hr>

#### Community Note

Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request.
Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request.
If you are interested in working on this issue or have submitted a pull request, please leave a comment.

#### Description

From the documentation doesn't seems to be possible to have a packer build with the `vsphere-iso` builder specifying a Storage DRS, only a specific Datastore is allowed. I've tried to set the Storage DRS name as the `datastore` property but it fails with:

```
==> vsphere-iso.packer-build-name: error creating vm: datastore 'Storage-DRS-name' not found
```

#### Use Case(s)

Usually using the Storage DRS allows to keep the same configuration even if new datastores are added for more storage.

#### Potential configuration

I'm not sure if new configuration would be required. Packer could first search for Storage DRS and look for a match of the `datastore` property and if found use it otherwise look for a match of single datastores.

#### Potential References

https://docs.vmware.com/en/VMware-vSphere/6.7/com.vmware.vsphere.resmgmt.doc/GUID-827DBD6D-08B7-4411-9214-9E126671457F.html


Hints from issue comments:
@tenthirtyam: Recommend updating the context of this issue. The enhancement request is the to support a **datastore cluster**.

https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.resmgmt.doc/GUID-A2294106-A2B5-4AE4-8F45-9D22B20B5146.html

Ryan@FlorianLaunay: Hi @tenthirtyam. It may be a fantastic feature! Will it be planned in the next milestone ?@DonBower: This seems like a highly valued feature.  Is there an ETA?@Shackelford-Arden: I wanted to note that Terraform currently supports using datastore clusters:

https://registry.terraform.io/providers/hashicorp/vsphere/latest/docs/data-sources/datastore_cluster

Would be nice to be able to follow a similar pattern here in Packer.@tenthirtyam: Labels updated. The scope would need cover each of the builders a post-processors.@Zheer09: This is very essential to have and this might be a blocker for us that we don't have this feature , as we have up coming project we really needed this feature.

We would appreciate if this feature will be available soon in v1.4.1 @tenthirtyam.@tenthirtyam: Per the milestones, this would be targeted for v1.8.0 minor release based on priorities and available personal time to work on the project.@Zheer09: Appreciated your hard work@tenthirtyam: > [!NOTE]
> Self-assigned this enhancement request for an upcoming v2.x release.

## Overview

Adds datastore cluster support to the Packer Plugin for VMware vSphere builders and post-processors. Currently, users must specify individual datastores via the `datastore` parameter. This enhancement introduces a `datastore_cluster` parameter that leverages vSphere Storage DRS for automatic datastore selection based on utilization, I/O load, and storage policies.

The `datastore` and `datastore_cluster` parameters will be mutually exclusive, with Storage DRS recommendations automatically applied when using datastore clusters.

## Requirements

### Requirement 1: Backward Compatibility

**User Story:** As a DevOps engineer using the Packer plugin for VMware vSphere, I want backward compatibility maintained when upgrading to support datastore clusters, so that existing configurations continue working without modification.

#### Acceptance Criteria

1. WHEN existing configurations use only the `datastore` parameter THEN they SHALL continue working unchanged after plugin upgrade.
2. WHEN migrating from `datastore` to `datastore_cluster` THEN no other configuration parameters SHALL require modification.
3. WHEN the plugin is upgraded THEN existing Packer templates SHALL continue to function without any template changes.
4. WHEN environments use both `datastore` and `datastore_cluster` configurations THEN they SHALL coexist without conflicts or interference.
5. WHEN configuration validation occurs THEN existing `datastore` configurations SHALL not trigger new validation errors.

### Requirement 2: Datastore Cluster Configuration

**User Story:** As a DevOps engineer using the Packer plugin for VMware vSphere, I want to configure a datastore cluster instead of a specific datastore, so that Storage DRS can automatically place my virtual machines on the optimal datastore based on current conditions.

#### Acceptance Criteria

1. WHEN `datastore_cluster` is specified in configuration THEN the system SHALL use the cluster for virtual machine placement.
2. WHEN both `datastore` and `datastore_cluster` are specified THEN the system SHALL return a validation error indicating mutual exclusivity.
3. WHEN neither parameter is specified AND the host has multiple datastores THEN the system SHALL require one parameter to be specified.
4. WHEN `datastore_cluster` is specified THEN the system SHALL validate that the cluster exists and is accessible before proceeding.
5. WHEN `datastore_cluster` is specified THEN the system SHALL verify the user has sufficient permissions to access the cluster.

### Requirement 3: Storage DRS Integration

**User Story:** As a DevOps engineer using the Packer plugin for VMware vSphere, I want Storage DRS recommendations automatically applied when using datastore clusters, so that virtual machine placement follows configured storage policies without manual intervention.

#### Acceptance Criteria

1. WHEN using a datastore cluster THEN the system SHALL retrieve Storage DRS placement recommendations from vCenter.
2. WHEN Storage DRS recommendations are available THEN the system SHALL apply the recommended datastore selection.
3. WHEN Storage DRS recommendations cannot be retrieved OR fail THEN the system SHALL fall back to selecting any available datastore within the cluster.
4. WHEN datastore selection occurs THEN the system SHALL log the chosen datastore and selection method for audit purposes.
5. WHEN Storage DRS is disabled on the cluster THEN the system SHALL log a warning and proceed with round-robin or first-available selection.

### Requirement 4: Multi-Builder Support

**User Story:** As a DevOps engineer using the Packer plugin for VMware vSphere, I want consistent datastore cluster configuration across all builders, so that I can use the same configuration pattern regardless of build method.

#### Acceptance Criteria

1. WHEN `datastore_cluster` is used in `vsphere-iso` builder THEN it SHALL function for virtual machine creation operations.
2. WHEN `datastore_cluster` is used in `vsphere-clone` builder THEN it SHALL function for virtual machine cloning operations.
4. WHEN using `datastore_cluster` THEN configuration syntax SHALL be identical across all builder types.
5. WHEN `datastore_cluster` is used with additional storage disks THEN all disks SHALL be placed according to Storage DRS recommendations.

### Requirement 5: Post-Processor Support

**User Story:** As a DevOps engineer using the Packer plugin for VMware vSphere, I want datastore cluster support in post-processors, so that template creation and other post-processing operations use the same intelligent storage selection logic.

#### Acceptance Criteria

1. WHEN `datastore_cluster` is configured in `vsphere` post-processor THEN it SHALL use the cluster for post-processing operations.
3. WHEN post-processors inherit configuration from builders THEN datastore cluster settings SHALL be preserved and applied.

### Requirement 6: Performance and Resource Management

**User Story:** As a DevOps engineer using the Packer plugin for VMware vSphere, I want datastore cluster operations to be performant and not impact Storage DRS operations, so that my builds complete efficiently without affecting production workloads.

#### Acceptance Criteria

1. WHEN requesting Storage DRS recommendations THEN the system SHALL use appropriate timeout values to prevent hanging builds.
2. WHEN Storage DRS recommendations take longer than expected THEN the system SHALL fall back to manual selection after a configurable timeout.
3. WHEN multiple Packer builds use the same datastore cluster simultaneously THEN they SHALL not interfere with each other's Storage DRS requests.
4. WHEN Storage DRS recommendations are cached THEN the cache SHALL have appropriate expiration to balance performance and accuracy.@tenthirtyam: [#574](https://github.com/hashicorp/packer-plugin-vsphere/pull/574) has been merged.

We'll target the v2.1.0 release in January 2026.

Solution patch:
diff --git a/.web-docs/components/builder/vsphere-clone/README.md b/.web-docs/components/builder/vsphere-clone/README.md
index cac6d92..f1600be 100644
--- a/.web-docs/components/builder/vsphere-clone/README.md
+++ b/.web-docs/components/builder/vsphere-clone/README.md
@@ -1036,8 +1036,8 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
   Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
   section for more details.
 
-- `host` (string) - The ESXi host where the virtual machine is created. A full path must be
-  specified if the ESXi host is in a folder. For example `folder/host`.
+- `host` (string) - The ESX host where the virtual machine is created. A full path must be specified
+  if the ESX host is in a folder. For example `folder/host`.
   Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
   section for more details.
 
@@ -1050,9 +1050,17 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
   a nested path might resemble 'rp-packer/rp-linux-images'.
 
 - `datastore` (string) - The datastore where the virtual machine is created.
-  Required if `host` is a cluster, or if `host` has multiple datastores.
+  Required if `host` is a cluster or if `host` has multiple datastores,
+  unless `datastore_cluster` is specified.
+  
+  ~> **Note:** Cannot be used with `datastore_cluster`.
+
+- `datastore_cluster` (string) - The datastore cluster where the virtual machine is created.
+  When specified, Storage DRS will automatically select the optimal datastore.
+  
+  ~> **Note:** Cannot be used with `datastore`.
 
-- `set_host_for_datastore_uploads` (bool) - The ESXI host used for uploading files to the datastore.
+- `set_host_for_datastore_uploads` (bool) - The ESX host used for uploading files to the datastore.
   Defaults to `false`.
 
 <!-- End of code generated from the comments of the LocationConfig struct in builder/vsphere/common/config_location.go; -->
diff --git a/.web-docs/components/builder/vsphere-iso/README.md b/.web-docs/components/builder/vsphere-iso/README.md
index 990f564..f184202 100644
--- a/.web-docs/components/builder/vsphere-iso/README.md
+++ b/.web-docs/components/builder/vsphere-iso/README.md
@@ -142,8 +142,8 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
   Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
   section for more details.
 
-- `host` (string) - The ESXi host where the virtual machine is created. A full path must be
-  specified if the ESXi host is in a folder. For example `folder/host`.
+- `host` (string) - The ESX host where the virtual machine is created. A full path must be specified
+  if the ESX host is in a folder. For example `folder/host`.
   Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
   section for more details.
 
@@ -156,9 +156,17 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
   a nested path might resemble 'rp-packer/rp-linux-images'.
 
 - `datastore` (string) - The datastore where the virtual machine is created.
-  Required if `host` is a cluster, or if `host` has multiple datastores.
+  Required if `host` is a cluster or if `host` has multiple datastores,
+  unless `datastore_cluster` is specified.
+  
+  ~> **Note:** Cannot be used with `datastore_cluster`.
+
+- `datastore_cluster` (string) - The datastore cluster where the virtual machine is created.
+  When specified, Storage DRS will automatically select the optimal datastore.
+  
+  ~> **Note:** Cannot be used with `datastore`.
 
-- `set_host_for_datastore_uploads` (bool) - The ESXI host used for uploading files to the datastore.
+- `set_host_for_datastore_uploads` (bool) - The ESX host used for uploading files to the datastore.
   Defaults to `false`.
 
 <!-- End of code generated from the comments of the LocationConfig struct in builder/vsphere/common/config_location.go; -->
diff --git a/.web-docs/components/post-processor/vsphere/README.md b/.web-docs/components/post-processor/vsphere/README.md
index a97baa3..363dc48 100644
--- a/.web-docs/components/post-processor/vsphere/README.md
+++ b/.web-docs/components/post-processor/vsphere/README.md
@@ -23,15 +23,13 @@ The following configuration options are available for the post-processor.
 
 <!-- Code generated from the comments of the Config struct in post-processor/vsphere/post-processor.go; DO NOT EDIT MANUALLY -->
 
-- `cluster` (string) - The cluster or ESX host to upload the virtual machine.
-  This can be either the name of the vSphere cluster or the fully qualified domain name (FQDN)
-  or IP address of the ESX host.
+- `cluster` (string) - The cluster or ESX host to upload the virtual machine. This can be either the
+  name of the vSphere cluster or the fully qualified domain name (FQDN) or IP
+  address of the ESX host.
 
 - `datacenter` (string) - The name of the vSphere datacenter object to place the virtual machine.
   This is _not required_ if `resource_pool` is specified.
 
-- `datastore` (string) - The name of the vSphere datastore to place the virtual machine.
-
 - `host` (string) - The fully qualified domain name or IP address of the vCenter instance or ESX host.
 
 - `password` (string) - The password to use to authenticate to the vSphere endpoint.
@@ -45,6 +43,13 @@ The following configuration options are available for the post-processor.
 
 <!-- Code generated from the comments of the Config struct in post-processor/vsphere/post-processor.go; DO NOT EDIT MANUALLY -->
 
+- `datastore` (string) - The name of the vSphere datastore to place the virtual machine.
+  Mutually exclusive with `datastore_cluster`.
+
+- `datastore_cluster` (string) - The name of the vSphere datastore cluster to place the virtual machine.
+  When specified, Storage DRS will automatically select the optimal datastore.
+  Mutually exclusive with `datastore`.
+
 - `disk_mode` (string) - The disk format of the target virtual machine. One of `thin`, `thick`,
 
 - `esxi_host` (string) - The fully qualified domain name or IP address of the ESX host to upload the
@@ -55,7 +60,8 @@ The following configuration options are available for the post-processor.
 - `options` ([]string) - Options to send to `ovftool` when uploading the virtual machine.
   Use `ovftool --help` to list all the options available.
 
-- `overwrite` (bool) - Overwrite existing files. Defaults to `false`.
+- `overwrite` (bool) - Overwrite existing files.
+  If `true`, forces overwrites of existing files. Defaults to `false`.
 
 - `resource_pool` (string) - The name of the resource pool to place the virtual machine.
 
@@ -68,14 +74,15 @@ The following configuration options are available for the post-processor.
 
 - `hardware_version` (string) - The maximum virtual hardware version for the deployed virtual machine.
   
-  It does not upgrade the virtual hardware version of the source VM. Instead, it limits the
-  virtual hardware version of the deployed virtual machine  to the specified version.
-  If the source virtual machine's hardware version is higher than the specified version, the
-  deployed virtual machine's hardware version will be downgraded to the specified version.
+  It does not upgrade the virtual hardware version of the source VM. Instead, it
+  limits the virtual hardware version of the deployed virtual machine to the
+  specified version. If the source virtual machine's hardware version is higher
+  than the specified version, the deployed virtual machine's hardware version will
+  be downgraded to the specified version.
   
-  If the source virtual machine's hardware version is lower than or equal to the specified
-  version, the deployed virtual machine's hardware version will be the same as the source
-  virtual machine's.
+  If the source virtual machine's hardware version is lower than or equal to the
+  specified version, the deployed virtual machine's hardware version will be the
+  same as the source virtual machine's.
   
   This option is useful when deploying to vCenter instance or an ESX host whose
   version is different than the one used to create the artifact.
diff --git a/builder/vsphere/clone/builder.go b/builder/vsphere/clone/builder.go
index ac886aa..ef533a0 100644
--- a/builder/vsphere/clone/builder.go
+++ b/builder/vsphere/clone/builder.go
@@ -44,6 +44,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
 		&common.StepConnect{
 			Config: &b.config.ConnectConfig,
 		},
+		&common.StepResolveDatastore{
+			Datastore:        b.config.Datastore,
+			DatastoreCluster: b.config.DatastoreCluster,
+			DiskCount:        len(b.config.StorageConfig.Storage),
+		},
 		&commonsteps.StepCreateCD{
 			Files:   b.config.CDFiles,
 			Content: b.config.CDContent,
diff --git a/builder/vsphere/clone/config.hcl2spec.go b/builder/vsphere/clone/config.hcl2spec.go
index 985f456..c8814f2 100644
--- a/builder/vsphere/clone/config.hcl2spec.go
+++ b/builder/vsphere/clone/config.hcl2spec.go
@@ -50,6 +50,7 @@ type FlatConfig struct {
 	Host                            *string                                     `mapstructure:"host" cty:"host" hcl:"host"`
 	ResourcePool                    *string                                     `mapstructure:"resource_pool" cty:"resource_pool" hcl:"resource_pool"`
 	Datastore                       *string                                     `mapstructure:"datastore" cty:"datastore" hcl:"datastore"`
+	DatastoreCluster                *string                                     `mapstructure:"datastore_cluster" cty:"datastore_cluster" hcl:"datastore_cluster"`
 	SetHostForDatastoreUploads      *bool                                       `mapstructure:"set_host_for_datastore_uploads" cty:"set_host_for_datastore_uploads" hcl:"set_host_for_datastore_uploads"`
 	CPUs                            *int32                                      `mapstructure:"CPUs" cty:"CPUs" hcl:"CPUs"`
 	CpuCores                        *int32                                      `mapstructure:"cpu_cores" cty:"cpu_cores" hcl:"cpu_cores"`
@@ -204,6 +205,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
 		"host":                           &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
 		"resource_pool":                  &hcldec.AttrSpec{Name: "resource_pool", Type: cty.String, Required: false},
 		"datastore":                      &hcldec.AttrSpec{Name: "datastore", Type: cty.String, Required: false},
+		"datastore_cluster":              &hcldec.AttrSpec{Name: "datastore_cluster", Type: cty.String, Required: false},
 		"set_host_for_datastore_uploads": &hcldec.AttrSpec{Name: "set_host_for_datastore_uploads", Type: cty.Bool, Required: false},
 		"CPUs":                           &hcldec.AttrSpec{Name: "CPUs", Type: cty.Number, Required: false},
 		"cpu_cores":                      &hcldec.AttrSpec{Name: "cpu_cores", Type: cty.Number, Required: false},
diff --git a/builder/vsphere/clone/step_clone.go b/builder/vsphere/clone/step_clone.go
index 01b2bb3..61c4f26 100644
--- a/builder/vsphere/clone/step_clone.go
+++ b/builder/vsphere/clone/step_clone.go
@@ -9,6 +9,7 @@ package clone
 import (
 	"context"
 	"fmt"
+	"log"
 	"path"
 	"strings"
 
@@ -17,6 +18,7 @@ import (
 	"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common"
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
+	"github.com/vmware/govmomi/vim25/types"
 )
 
 type vAppConfig struct {
@@ -159,13 +161,61 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
 		})
 	}
 
+	datastoreName := s.Location.Datastore
+	var primaryDatastore driver.Datastore
+	if ds, ok := state.GetOk("datastore"); ok {
+		primaryDatastore = ds.(driver.Datastore)
+		datastoreName = primaryDatastore.Name()
+	}
+
+	// If no datastore was resolved and no datastore was specified, return an error.
+	if datastoreName == "" && s.Location.DatastoreCluster == "" {
+		state.Put("error", fmt.Errorf("no datastore specified and no datastore resolved from cluster"))
+		return multistep.ActionHalt
+	}
+
+	// Handle multi-disk placement when using a datastore cluster.
+	var datastoreRefs []*types.ManagedObjectReference
+	if s.Location.DatastoreCluster != "" && len(disks) > 1 {
+		if vcDriver, ok := d.(*driver.VCenterDriver); ok {
+			// Request Storage DRS recommendations for all disks at once for optimal placement.
+			ui.Sayf("Requesting Storage DRS recommendations for %d disks...", len(disks))
+
+			diskDatastores, method, err := vcDriver.SelectDatastoresForDisks(s.Location.DatastoreCluster, disks)
+			if err != nil {
+				ui.Errorf("Warning: Failed to get Storage DRS recommendations: %s. Using primary datastore.", err)
+				if primaryDatastore != nil {
+					ref := primaryDatastore.Reference()
+					for i := 0; i < len(disks); i++ {
+						datastoreRefs = append(datastoreRefs, &ref)
+					}
+				}
+			} else {
+				// Use the first disk's datastore as the primary datastore.
+				if len(diskDatastores) > 0 {
+					datastoreName = diskDatastores[0].Name()
+				}
+
+				for i, ds := range diskDatastores {
+					ref := ds.Reference()
+					if method == driver.SelectionMethodDRS {
+						log.Printf("[INFO] Disk %d: Storage DRS selected datastore '%s'", i+1, ds.Name())
+					} else {
+						log.Printf("[INFO] Disk %d: Using first available datastore '%s'", i+1, ds.Name())
+					}
+					datastoreRefs = append(datastoreRefs, &ref)
+				}
+			}
+		}
+	}
+
 	vm, err := template.Clone(ctx, &driver.CloneConfig{
 		Name:            s.Location.VMName,
 		Folder:          s.Location.Folder,
 		Cluster:         s.Location.Cluster,
 		Host:            s.Location.Host,
 		ResourcePool:    s.Location.ResourcePool,
-		Datastore:       s.Location.Datastore,
+		Datastore:       datastoreName,
 		LinkedClone:     s.Config.LinkedClone,
 		Network:         s.Config.Network,
 		MacAddress:      strings.ToLower(s.Config.MacAddress),
@@ -175,6 +225,7 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
 		StorageConfig: driver.StorageConfig{
 			DiskControllerType: s.Config.StorageConfig.DiskControllerType,
 			Storage:            disks,
+			DatastoreRefs:      datastoreRefs,
 		},
 	})
 	if err != nil {
diff --git a/builder/vsphere/common/config_location.go b/builder/vsphere/common/config_location.go
index b8104e4..55b8a26 100644
--- a/builder/vsphere/common/config_location.go
+++ b/builder/vsphere/common/config_location.go
@@ -21,8 +21,8 @@ type LocationConfig struct {
 	// Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
 	// section for more details.
 	Cluster string `mapstructure:"cluster"`
-	// The ESXi host where the virtual machine is created. A full path must be
-	// specified if the ESXi host is in a folder. For example `folder/host`.
+	// The ESX host where the virtual machine is created. A full path must be specified
+	// if the ESX host is in a folder. For example `folder/host`.
 	// Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
 	// section for more details.
 	Host string `mapstructure:"host"`
@@ -35,9 +35,17 @@ type LocationConfig struct {
 	// a nested path might resemble 'rp-packer/rp-linux-images'.
 	ResourcePool string `mapstructure:"resource_pool"`
 	// The datastore where the virtual machine is created.
-	// Required if `host` is a cluster, or if `host` has multiple datastores.
+	// Required if `host` is a cluster or if `host` has multiple datastores,
+	// unless `datastore_cluster` is specified.
+	//
+	// ~> **Note:** Cannot be used with `datastore_cluster`.
 	Datastore string `mapstructure:"datastore"`
-	// The ESXI host used for uploading files to the datastore.
+	// The datastore cluster where the virtual machine is created.
+	// When specified, Storage DRS will automatically select the optimal datastore.
+	//
+	// ~> **Note:** Cannot be used with `datastore`.
+	DatastoreCluster string `mapstructure:"datastore_cluster"`
+	// The ESX host used for uploading files to the datastore.
 	// Defaults to `false`.
 	SetHostForDatastoreUploads bool `mapstructure:"set_host_for_datastore_uploads"`
 }
@@ -52,7 +60,10 @@ func (c *LocationConfig) Prepare() []error {
 		errs = append(errs, fmt.Errorf("'host' or 'cluster' is required"))
 	}
 
-	// clean Folder path and remove leading slash as folders are relative within vsphere
+	if c.Datastore != "" && c.DatastoreCluster != "" {
+		errs = append(errs, fmt.Errorf("'datastore' and 'datastore_cluster' are mutually exclusive; specify only one"))
+	}
+
 	c.Folder = path.Clean(c.Folder)
 	c.Folder = strings.TrimLeft(c.Folder, "/")
 
diff --git a/builder/vsphere/common/config_location.hcl2spec.go b/builder/vsphere/common/config_location.hcl2spec.go
index 62cb720..ecd8f70 100644
--- a/builder/vsphere/common/config_location.hcl2spec.go
+++ b/builder/vsphere/common/config_location.hcl2spec.go
@@ -16,6 +16,7 @@ type FlatLocationConfig struct {
 	Host                       *string `mapstructure:"host" cty:"host" hcl:"host"`
 	ResourcePool               *string `mapstructure:"resource_pool" cty:"resource_pool" hcl:"resource_pool"`
 	Datastore                  *string `mapstructure:"datastore" cty:"datastore" hcl:"datastore"`
+	DatastoreCluster           *string `mapstructure:"datastore_cluster" cty:"datastore_cluster" hcl:"datastore_cluster"`
 	SetHostForDatastoreUploads *bool   `mapstructure:"set_host_for_datastore_uploads" cty:"set_host_for_datastore_uploads" hcl:"set_host_for_datastore_uploads"`
 }
 
@@ -37,6 +38,7 @@ func (*FlatLocationConfig) HCL2Spec() map[string]hcldec.Spec {
 		"host":                           &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
 		"resource_pool":                  &hcldec.AttrSpec{Name: "resource_pool", Type: cty.String, Required: false},
 		"datastore":                      &hcldec.AttrSpec{Name: "datastore", Type: cty.String, Required: false},
+		"datastore_cluster":              &hcldec.AttrSpec{Name: "datastore_cluster", Type: cty.String, Required: false},
 		"set_host_for_datastore_uploads": &hcldec.AttrSpec{Name: "set_host_for_datastore_uploads", Type: cty.Bool, Required: false},
 	}
 	return s
diff --git a/builder/vsphere/common/step_add_floppy.go b/builder/vsphere/common/step_add_floppy.go
index 06ffdf8..1d22be6 100644
--- a/builder/vsphere/common/step_add_floppy.go
+++ b/builder/vsphere/common/step_add_floppy.go
@@ -63,10 +63,18 @@ func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multist
 	if floppyPath, ok := state.GetOk("floppy_path"); ok {
 		ui.Say("Uploading floppy image...")
 
-		ds, err := d.FindDatastore(s.Datastore, s.Host)
-		if err != nil {
-			state.Put("error", err)
-			return multistep.ActionHalt
+		var ds driver.Datastore
+		var err error
+
+		// If a datastore was resolved (from datastore or datastore_cluster), use it.
+		if resolvedDs, ok := state.GetOk("datastore"); ok {
+			ds = resolvedDs.(driver.Datastore)
+		} else {
+			ds, err = d.FindDatastore(s.Datastore, s.Host)
+			if err != nil {
+				state.Put("error", err)
+				return multistep.ActionHalt
+			}
 		}
 		vmDir, err := vm.GetDir()
 		if err != nil {
@@ -123,10 +131,18 @@ func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
 	if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
 		ui.Say("Deleting floppy image...")
 
-		ds, err := d.FindDatastore(s.Datastore, s.Host)
-		if err != nil {
-			state.Put("error", err)
-			return
+		var ds driver.Datastore
+		var err error
+
+		// If a datastore was resolved (from datastore or datastore_cluster), use it.
+		if resolvedDs, ok := state.GetOk("datastore"); ok {
+			ds = resolvedDs.(driver.Datastore)
+		} else {
+			ds, err = d.FindDatastore(s.Datastore, s.Host)
+			if err != nil {
+				state.Put("error", err)
+				return
+			}
 		}
 
 		err = ds.Delete(UploadedFloppyPath.(string))
diff --git a/builder/vsphere/common/step_download.go b/builder/vsphere/common/step_download.go
index c00b1f7..4313793 100644
--- a/builder/vsphere/common/step_download.go
+++ b/builder/vsphere/common/step_download.go
@@ -46,7 +46,7 @@ type StepDownload struct {
 }
 
 func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
-	driver := state.Get("driver").(driver.Driver)
+	d := state.Get("driver").(driver.Driver)
 	ui := state.Get("ui").(packersdk.Ui)
 
 	// Set the remote cache datastore. If not set, use the default datastore for the build.
@@ -55,11 +55,19 @@ func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multis
 		remoteCacheDatastore = s.RemoteCacheDatastore
 	}
 
-	// Find the datastore to use for the remote cache.
-	ds, err := driver.FindDatastore(remoteCacheDatastore, s.Host)
-	if err != nil {
-		state.Put("error", fmt.Errorf("error finding the datastore: %v", err))
-		return multistep.ActionHalt
+	var ds driver.Datastore
+	var err error
+
+	// If a datastore was resolved (from datastore or datastore_cluster), use it.
+	if resolvedDs, ok := state.GetOk("datastore"); ok && remoteCacheDatastore == s.Datastore {
+		ds = resolvedDs.(driver.Datastore)
+	} else {
+		// Find the datastore to use for the remote cache.
+		ds, err = d.FindDatastore(remoteCacheDatastore, s.Host)
+		if err != nil {
+			state.Put("error", fmt.Errorf("error finding the datastore: %v", err))
+			return multistep.ActionHalt
+		}
 	}
 
 	// Set the remote cache path. If not set, use the default cache path.
diff --git a/builder/vsphere/common/step_remote_upload.go b/builder/vsphere/common/step_remote_upload.go
index a0e1174..fbd05e8 100644
--- a/builder/vsphere/common/step_remote_upload.go
+++ b/builder/vsphere/common/step_remote_upload.go
@@ -32,7 +32,7 @@ func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
 
 	if path, ok := state.GetOk("iso_path"); ok {
 		// user-supplied boot iso
-		fullRemotePath, err := s.uploadFile(path.(string), d, ui)
+		fullRemotePath, err := s.uploadFile(path.(string), d, ui, state)
 		if err != nil {
 			state.Put("error", err)
 			return multistep.ActionHalt
@@ -41,7 +41,7 @@ func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
 	}
 	if cdPath, ok := state.GetOk("cd_path"); ok {
 		// Packer-created cd_files disk
-		fullRemotePath, err := s.uploadFile(cdPath.(string), d, ui)
+		fullRemotePath, err := s.uploadFile(cdPath.(string), d, ui, state)
 		if err != nil {
 			state.Put("error", err)
 			return multistep.ActionHalt
@@ -66,7 +66,7 @@ func GetRemoteDirectoryAndPath(path string, ds driver.Datastore, remoteCachePath
 	return filename, remotePath, remoteDirectory, fullRemotePath
 }
 
-func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packersdk.Ui) (string, error) {
+func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packersdk.Ui, state multistep.StateBag) (string, error) {
 
 	// Set the remote cache datastore. If not set, use the default datastore for the build.
 	remoteCacheDatastore := s.Datastore
@@ -74,10 +74,18 @@ func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packersdk
 		remoteCacheDatastore = s.RemoteCacheDatastore
 	}
 
-	// Find the datastore to use for the remote cache.
-	ds, err := d.FindDatastore(remoteCacheDatastore, s.Host)
-	if err != nil {
-		return "", fmt.Errorf("error finding the remote cache datastore: %v", err)
+	var ds driver.Datastore
+	var err error
+
+	// If a datastore was resolved (from datastore or datastore_cluster), use it.
+	if resolvedDs, ok := state.GetOk("datastore"); ok && remoteCacheDatastore == s.Datastore {
+		ds = resolvedDs.(driver.Datastore)
+	} else {
+		// Find the datastore to use for the remote cache.
+		ds, err = d.FindDatastore(remoteCacheDatastore, s.Host)
+		if err != nil {
+			return "", fmt.Errorf("error finding the remote cache datastore: %v", err)
+		}
 	}
 
 	// Set the remote cache path. If not set, use the default cache path.
@@ -143,10 +151,18 @@ func (s *StepRemoteUpload) Cleanup(state multistep.StateBag) {
 	d := state.Get("driver").(*driver.VCenterDriver)
 	ui.Sayf("Removing %s...", UploadedCDPath)
 
-	ds, err := d.FindDatastore(s.Datastore, s.Host)
-	if err != nil {
-		ui.Sayf("Unable to find the remote cache datastore. Please remove the item manually: %s", err)
-		return
+	var ds driver.Datastore
+	var err error
+
+	// If a datastore was resolved (from datastore or datastore_cluster), use it.
+	if resolvedDs, ok := state.GetOk("datastore"); ok {
+		ds = resolvedDs.(driver.Datastore)
+	} else {
+		ds, err = d.FindDatastore(s.Datastore, s.Host)
+		if err != nil {
+			ui.Sayf("Unable to find the remote cache datastore. Please remove the item manually: %s", err)
+			return
+		}
 	}
 
 	err = ds.Delete(UploadedCDPath.(string))
diff --git a/builder/vsphere/common/step_remove_floppy.go b/builder/vsphere/common/step_remove_floppy.go
index 8adafd2..c1f2fd9 100644
--- a/builder/vsphere/common/step_remove_floppy.go
+++ b/builder/vsphere/common/step_remove_floppy.go
@@ -34,10 +34,18 @@ func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) mult
 
 	if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
 		ui.Say("Deleting floppy image...")
-		ds, err := d.FindDatastore(s.Datastore, s.Host)
-		if err != nil {
-			state.Put("error", err)
-			return multistep.ActionHalt
+
+		var ds driver.Datastore
+
+		// If a datastore was resolved (from datastore or datastore_cluster), use it.
+		if resolvedDs, ok := state.GetOk("datastore"); ok {
+			ds = resolvedDs.(driver.Datastore)
+		} else {
+			ds, err = d.FindDatastore(s.Datastore, s.Host)
+			if err != nil {
+				state.Put("error", err)
+				return multistep.ActionHalt
+			}
 		}
 		if err := ds.Delete(UploadedFloppyPath.(string)); err != nil {
 			state.Put("error", err)
diff --git a/builder/vsphere/common/step_resolve_datastore.go b/builder/vsphere/common/step_resolve_datastore.go
new file mode 100644
index 0000000..375870f
--- /dev/null
+++ b/builder/vsphere/common/step_resolve_datastore.go
@@ -0,0 +1,90 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package common
+
+import (
+	"context"
+	"fmt"
+	"log"
+
+	"github.com/hashicorp/packer-plugin-sdk/multistep"
+	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
+)
+
+// DatastoreClusterDriver is an interface for drivers that support datastore cluster operations.
+type DatastoreClusterDriver interface {
+	driver.Driver
+	SelectDatastoreFromCluster(clusterName string) (driver.Datastore, string, error)
+}
+
+// StepResolveDatastore resolves a datastore from either a direct datastore
+// name or a datastore cluster name. When a datastore cluster is specified,
+// it uses Storage DRS to select the optimal datastore.
+type StepResolveDatastore struct {
+	// Datastore is the name of a specific datastore to use.
+	Datastore string
+	// DatastoreCluster is the name of a datastore cluster to use.
+	// When specified, Storage DRS will select the optimal datastore.
+	DatastoreCluster string
+	// DiskCount is the number of disks that will be created.
+	// When using a datastore cluster with multiple disks, this step will be skipped
+	// to avoid redundant DRS calls (per-disk DRS calls will be made later).
+	DiskCount int
+}
+
+// Run resolves a datastore from either a direct datastore name or a datastore cluster.
+// When using a datastore cluster, Storage DRS selects the optimal datastore.
+// For multi-disk configurations with datastore clusters, this initial selection is used
+// for non-disk operations (ISO uploads, etc.), while per-disk DRS calls are made separately.
+func (s *StepResolveDatastore) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
+	d := state.Get("driver").(driver.Driver)
+
+	if s.Datastore == "" && s.DatastoreCluster == "" {
+		return multistep.ActionContinue
+	}
+
+	var ds driver.Datastore
+	var err error
+	var selectionMethod string
+
+	if s.DatastoreCluster != "" {
+		clusterDriver, ok := d.(DatastoreClusterDriver)
+		if !ok {
+			state.Put("error", fmt.Errorf("driver does not support datastore cluster operations"))
+			return multistep.ActionHalt
+		}
+
+		ds, selectionMethod, err = clusterDriver.SelectDatastoreFromCluster(s.DatastoreCluster)
+		if err != nil {
+			state.Put("error", fmt.Errorf("error resolving datastore from cluster '%s': %s", s.DatastoreCluster, err))
+			return multistep.ActionHalt
+		}
+
+		if s.DiskCount > 1 {
+			log.Printf("[INFO] Selected datastore '%s' from cluster '%s' for non-disk operations (per-disk placement will be requested separately)", ds.Name(), s.DatastoreCluster)
+		} else {
+			if selectionMethod == driver.SelectionMethodDRS {
+				log.Printf("[INFO] Storage DRS selected datastore '%s' from cluster '%s'", ds.Name(), s.DatastoreCluster)
+			} else {
+				log.Printf("[INFO] Selected datastore '%s' from cluster '%s' (first available)", ds.Name(), s.DatastoreCluster)
+			}
+		}
+	} else {
+		log.Printf("[INFO] Using datastore '%s'", s.Datastore)
+		ds, err = d.FindDatastore(s.Datastore, "")
+		if err != nil {
+			state.Put("error", fmt.Errorf("error finding datastore '%s': %s", s.Datastore, err))
+			return multistep.ActionHalt
+		}
+		selectionMethod = "direct"
+	}
+
+	state.Put("datastore", ds)
+	state.Put("datastore_selection_method", selectionMethod)
+
+	return multistep.ActionContinue
+}
+
+// Cleanup performs any necessary cleanup.
+func (s *StepResolveDatastore) Cleanup(state multistep.StateBag) {}
diff --git a/builder/vsphere/driver/datastore_cluster.go b/builder/vsphere/driver/datastore_cluster.go
new file mode 100644
index 0000000..b6a1ea1
--- /dev/null
+++ b/builder/vsphere/driver/datastore_cluster.go
@@ -0,0 +1,99 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package driver
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+// DatastoreCluster represents a vSphere datastore cluster (storage pod).
+type DatastoreCluster interface {
+	// Name returns the name of the datastore cluster.
+	Name() string
+
+	// Reference returns the managed object reference.
+	Reference() types.ManagedObjectReference
+
+	// ListDatastores returns all datastores in the cluster.
+	ListDatastores() ([]Datastore, error)
+}
+
+// DatastoreClusterDriver implements the DatastoreCluster interface.
+type DatastoreClusterDriver struct {
+	cluster *object.StoragePod
+	driver  *VCenterDriver
+}
+
+// Name returns the name of the datastore cluster.
+func (dsc *DatastoreClusterDriver) Name() string {
+	return dsc.cluster.Name()
+}
+
+// Reference returns the managed object reference of the datastore cluster.
+func (dsc *DatastoreClusterDriver) Reference() types.ManagedObjectReference {
+	return dsc.cluster.Reference()
+}
+
+// ListDatastores returns all datastores in the cluster.
+func (dsc *DatastoreClusterDriver) ListDatastores() ([]Datastore, error) {
+	datastores, err := dsc.cluster.Children(dsc.driver.Ctx)
+	if err != nil {
+		return nil, fmt.Errorf("error listing datastores in cluster '%s': %s", dsc.Name(), err)
+	}
+
+	var result []Datastore
+	for _, dsRef := range datastores {
+		// Try to get the datastore via finder first for proper initialization.
+		ds, err := dsc.driver.Finder.Datastore(dsc.driver.Ctx, dsRef.Reference().Value)
+		if err != nil {
+			// If finder fails, create from reference and fetch properties.
+			datastoreObj := object.NewDatastore(dsc.driver.Client.Client, dsRef.Reference())
+			dsDriver := &DatastoreDriver{
+				ds:     datastoreObj,
+				driver: dsc.driver,
+			}
+			info, err := dsDriver.Info("name")
+			if err != nil {
+				return nil, fmt.Errorf("error getting datastore info: %s", err)
+			}
+			if info.Name == "" {
+				return nil, fmt.Errorf("datastore has empty name")
+			}
+			result = append(result, dsDriver)
+		} else {
+			result = append(result, &DatastoreDriver{
+				ds:     ds,
+				driver: dsc.driver,
+			})
+		}
+	}
+
+	return result, nil
+}
+
+// FindDatastoreCluster locates a datastore cluster by name.
+// Returns a DatastoreCluster object or an error if the cluster is not found.
+func (d *VCenterDriver) FindDatastoreCluster(name string) (DatastoreCluster, error) {
+	storagePod, err := d.Finder.DatastoreCluster(d.Ctx, name)
+	if err != nil {
+		clusters, listErr := d.Finder.DatastoreClusterList(d.Ctx, "*")
+		if listErr == nil && len(clusters) > 0 {
+			var names []string
+			for _, c := range clusters {
+				names = append(names, c.Name())
+			}
+			return nil, fmt.Errorf("datastore cluster '%s' not found; available clusters: %s", name, strings.Join(names, ", "))
+		}
+		return nil, fmt.Errorf("error finding datastore cluster with name '%s': %s", name, err)
+	}
+
+	return &DatastoreClusterDriver{
+		cluster: storagePod,
+		driver:  d,
+	}, nil
+}
diff --git a/builder/vsphere/driver/datastore_cluster_mock.go b/builder/vsphere/driver/datastore_cluster_mock.go
new file mode 100644
index 0000000..549bb2f
--- /dev/null
+++ b/builder/vsphere/driver/datastore_cluster_mock.go
@@ -0,0 +1,49 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package driver
+
+import (
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+// DatastoreClusterMock is a mock implementation of the DatastoreCluster interface for testing.
+type DatastoreClusterMock struct {
+	NameReturn           string
+	ReferenceReturn      types.ManagedObjectReference
+	ListDatastoresReturn []Datastore
+	ListDatastoresErr    error
+	ListDatastoresCalled bool
+}
+
+// Name returns the mock name of the datastore cluster.
+func (dsc *DatastoreClusterMock) Name() string {
+	if dsc.NameReturn == "" {
+		return "datastore-cluster-mock"
+	}
+	return dsc.NameReturn
+}
+
+// Reference returns the mock managed object reference.
+func (dsc *DatastoreClusterMock) Reference() types.ManagedObjectReference {
+	if dsc.ReferenceReturn.Type == "" {
+		return types.ManagedObjectReference{
+			Type:  "StoragePod",
+			Value: "datastore-cluster-mock-ref",
+		}
+	}
+	return dsc.ReferenceReturn
+}
+
+// ListDatastores returns the mock list of datastores.
+func (dsc *DatastoreClusterMock) ListDatastores() ([]Datastore, error) {
+	dsc.ListDatastoresCalled = true
+	if dsc.ListDatastoresErr != nil {
+		return nil, dsc.ListDatastoresErr
+	}
+	if dsc.ListDatastoresReturn == nil {
+		// Return a default mock datastore if none specified
+		return []Datastore{&DatastoreMock{NameReturn: "datastore-1"}}, nil
+	}
+	return dsc.ListDatastoresReturn, nil
+}
diff --git a/builder/vsphere/driver/disk.go b/builder/vsphere/driver/disk.go
index dbc72e3..387693a 100644
--- a/builder/vsphere/driver/disk.go
+++ b/builder/vsphere/driver/disk.go
@@ -20,11 +20,13 @@ type Disk struct {
 type StorageConfig struct {
 	DiskControllerType []string
 	Storage            []Disk
+	DatastoreRefs      []*types.ManagedObjectReference
 }
 
-// AddStorageDevices adds virtual storage devices to an existing device list
-// based on the configuration. Adds a new controller for each controller type
-// specified in the configuration and adds virtual disks to the controller.
+// AddStorageDevices adds virtual storage devices to an existing device list.
+// It creates a controller for each type specified in DiskControllerType and attaches
+// virtual disks to the controllers. If DatastoreRefs is provided, each disk is placed
+// on the corresponding datastore; otherwise, disks inherit the VM's datastore.
 func (c *StorageConfig) AddStorageDevices(existingDevices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
 	newDevices := object.VirtualDeviceList{}
 
@@ -52,15 +54,21 @@ func (c *StorageConfig) AddStorageDevices(existingDevices object.VirtualDeviceLi
 		controllers = append(controllers, controller)
 	}
 
-	for _, dc := range c.Storage {
+	for i, dc := range c.Storage {
+		backing := &types.VirtualDiskFlatVer2BackingInfo{
+			DiskMode:        string(types.VirtualDiskModePersistent),
+			ThinProvisioned: types.NewBool(dc.DiskThinProvisioned),
+			EagerlyScrub:    types.NewBool(dc.DiskEagerlyScrub),
+		}
+
+		if i < len(c.DatastoreRefs) && c.DatastoreRefs[i] != nil {
+			backing.Datastore = c.DatastoreRefs[i]
+		}
+
 		disk := &types.VirtualDisk{
 			VirtualDevice: types.VirtualDevice{
-				Key: existingDevices.NewKey(),
-				Backing: &types.VirtualDiskFlatVer2BackingInfo{
-					DiskMode:        string(types.VirtualDiskModePersistent),
-					ThinProvisioned: types.NewBool(dc.DiskThinProvisioned),
-					EagerlyScrub:    types.NewBool(dc.DiskEagerlyScrub),
-				},
+				Key:     existingDevices.NewKey(),
+				Backing: backing,
 			},
 			CapacityInKB: dc.DiskSize * 1024,
 		}
@@ -73,8 +81,8 @@ func (c *StorageConfig) AddStorageDevices(existingDevices object.VirtualDeviceLi
 	return newDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
 }
 
-// findDisk scans a list of virtual devices and retrieves a single virtual disk
-// if exactly one is found.  Returns an error if no disk or multiple disks are found.
+// findDisk scans a list of virtual devices and retrieves a single virtual disk.
+// Returns an error if no disk or multiple disks are found.
 // TODO: Add support for multiple disks.
 func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) {
 	var disks []*types.VirtualDisk
@@ -87,12 +95,9 @@ func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) {
 
 	switch len(disks) {
 	case 0:
-		// No disks found.
 		return nil, errors.New("error finding virtual disk")
 	case 1:
-		// Single disk found.
 		return disks[0], nil
 	}
-	// Multiple disks found.
 	return nil, errors.New("more than one virtual disk found, only a single disk is allowed")
 }
diff --git a/builder/vsphere/driver/storage_drs.go b/builder/vsphere/driver/storage_drs.go
new file mode 100644
index 0000000..23eeef9
--- /dev/null
+++ b/builder/vsphere/driver/storage_drs.go
@@ -0,0 +1,290 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package driver
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"time"
+
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vim25/methods"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+const (
+	// StorageDRSTimeout is the maximum time to wait for a Storage DRS recommendation.
+	StorageDRSTimeout = 30 * time.Second
+
+	// SelectionMethodDRS indicates the datastore was selected using Storage DRS.
+	SelectionMethodDRS = "storage-drs"
+
+	// SelectionMethodFallback indicates the datastore was selected using first-available fallback.
+	SelectionMethodFallback = "first-available"
+)
+
+// RequestStoragePlacement requests a placement recommendation from Storage DRS.
+// It returns the placement result or an error if the request fails or times out.
+func (d *VCenterDriver) RequestStoragePlacement(
+	cluster types.ManagedObjectReference,
+	vmSpec types.VirtualMachineConfigSpec,
+	resourcePool *types.ManagedObjectReference,
+) (*types.StoragePlacementResult, error) {
+	ctx, cancel := context.WithTimeout(d.Ctx, StorageDRSTimeout)
+	defer cancel()
+
+	placementSpec := types.StoragePlacementSpec{
+		Type:         string(types.StoragePlacementSpecPlacementTypeCreate),
+		ConfigSpec:   &vmSpec,
+		ResourcePool: resourcePool,
+		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
+			StoragePod: &cluster,
+		},
+	}
+
+	storageResourceManager := d.VimClient.ServiceContent.StorageResourceManager
+	if storageResourceManager == nil {
+		return nil, fmt.Errorf("storage resource manager not available")
+	}
+
+	req := types.RecommendDatastores{
+		This:        *storageResourceManager,
+		StorageSpec: placementSpec,
+	}
+
+	res, err := methods.RecommendDatastores(ctx, d.VimClient, &req)
+	if err != nil {
+		return nil, fmt.Errorf("error requesting storage placement: %s", err)
+	}
+
+	if len(res.Returnval.Recommendations) == 0 {
+		return nil, fmt.Errorf("no storage placement recommendations returned")
+	}
+
+	return &res.Returnval, nil
+}
+
+// SelectDatastoresForDisks requests Storage DRS recommendations for multiple
+// disks at once. This allows Storage DRS to make optimal placement decisions.
+// Returns a slice of datastores (one per disk), selection method, and any
+// error.
+func (d *VCenterDriver) SelectDatastoresForDisks(
+	clusterName string,
+	disks []Disk,
+) ([]Datastore, string, error) {
+	cluster, err := d.FindDatastoreCluster(clusterName)
+	if err != nil {
+		return nil, "", err
+	}
+
+	datastores, err := cluster.ListDatastores()
+	if err != nil {
+		return nil, "", err
+	}
+
+	if len(datastores) == 0 {
+		return nil, "", fmt.Errorf("datastore cluster '%s' contains no available datastores", clusterName)
+	}
+
+	// Create a virtual machine spec with multiple disks for Storage DRS to
+	// evaluate for placement.
+	vmSpec := types.VirtualMachineConfigSpec{
+		Name:     fmt.Sprintf("packer-placement-request-%d", time.Now().UnixNano()),
+		NumCPUs:  1,
+		MemoryMB: 512,
+		Files: &types.VirtualMachineFileInfo{
+			VmPathName: fmt.Sprintf("[%s]", clusterName),
+		},
+	}
+
+	// Add disk device specs to the virtual machine config using actual disk
+	// configurations.
+	deviceList := object.VirtualDeviceList{}
+	controller, err := deviceList.CreateSCSIController("pvscsi")
+	if err != nil {
+		return nil, "", fmt.Errorf("error creating controller for DRS request: %s", err)
+	}
+	deviceList = append(deviceList, controller)
+
+	for i, diskConfig := range disks {
+		disk := &types.VirtualDisk{
+			VirtualDevice: types.VirtualDevice{
+				Key: int32(-100 - i),
+				Backing: &types.VirtualDiskFlatVer2BackingInfo{
+					DiskMode:        string(types.VirtualDiskModePersistent),
+					ThinProvisioned: types.NewBool(diskConfig.DiskThinProvisioned),
+					EagerlyScrub:    types.NewBool(diskConfig.DiskEagerlyScrub),
+				},
+			},
+			CapacityInKB: diskConfig.DiskSize * 1024,
+		}
+		deviceList.AssignController(disk, controller.(types.BaseVirtualController))
+		deviceList = append(deviceList, disk)
+	}
+
+	deviceSpecs, err := deviceList.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
+	if err != nil {
+		return nil, "", fmt.Errorf("error creating device specs for DRS request: %s", err)
+	}
+	vmSpec.DeviceChange = deviceSpecs
+
+	// Get resource pool for the Storage DRS request.
+	var resourcePoolRef *types.ManagedObjectReference
+	if len(datastores) > 0 {
+		dsInfo, err := datastores[0].Info("host")
+		if err == nil && len(dsInfo.Host) > 0 {
+			hostRef := dsInfo.Host[0].Key
+			host := object.NewHostSystem(d.Client.Client, hostRef)
+			hostInfo, err := host.ResourcePool(d.Ctx)
+			if err == nil {
+				ref := hostInfo.Reference()
+				resourcePoolRef = &ref
+			}
+		}
+	}
+
+	placementResult, err := d.RequestStoragePlacement(cluster.Reference(), vmSpec, resourcePoolRef)
+	if err == nil && placementResult != nil && len(placementResult.Recommendations) > 0 {
+		recommendation := placementResult.Recommendations[0]
+
+		if len(recommendation.Action) > 0 {
+			// Storage DRS typically returns one action when all disks should go
+			// to the same datastore.
+			var recommendedDatastore Datastore
+
+			for _, action := range recommendation.Action {
+				if relocateAction, ok := action.(*types.StoragePlacementAction); ok {
+					datastoreObj := object.NewDatastore(d.Client.Client, relocateAction.Destination)
+					dsDriver := &DatastoreDriver{
+						ds:     datastoreObj,
+						driver: d,
+					}
+					info, err := dsDriver.Info("name")
+					if err != nil {
+						log.Printf("[WARN] Failed to get datastore name: %s", err)
+						continue
+					}
+
+					ds, err := d.Finder.Datastore(d.Ctx, info.Name)
+					if err != nil {
+						log.Printf("[WARN] Failed to find datastore '%s': %s. Using direct reference.", info.Name, err)
+						recommendedDatastore = dsDriver
+					} else {
+						recommendedDatastore = &DatastoreDriver{ds: ds, driver: d}
+					}
+					break
+				}
+			}
+
+			if recommendedDatastore != nil {
+				result := make([]Datastore, len(disks))
+				for i := range len(disks) {
+					result[i] = recommendedDatastore
+				}
+				return result, SelectionMethodDRS, nil
+			}
+		}
+	}
+
+	// Fallback: Return first available datastore for all disks.
+	if err != nil {
+		log.Printf("[WARN] Storage DRS failed for cluster '%s': %s. Using first-available fallback.", clusterName, err)
+	}
+	result := make([]Datastore, len(disks))
+	for i := range len(disks) {
+		result[i] = datastores[0]
+	}
+	return result, SelectionMethodFallback, nil
+}
+
+// SelectDatastoreFromCluster selects a datastore from a cluster using Storage
+// DRS. It attempts to get a Storage DRS recommendation and falls back to the
+// first available datastore if Storage DRS fails or times out.
+func (d *VCenterDriver) SelectDatastoreFromCluster(
+	clusterName string,
+) (Datastore, string, error) {
+	cluster, err := d.FindDatastoreCluster(clusterName)
+	if err != nil {
+		return nil, "", err
+	}
+
+	datastores, err := cluster.ListDatastores()
+	if err != nil {
+		return nil, "", err
+	}
+
+	if len(datastores) == 0 {
+		return nil, "", fmt.Errorf("datastore cluster '%s' contains no available datastores", clusterName)
+	}
+
+	// Create a minimal virtual machine spec for the Storage DRS placement
+	// request. Use a timestamp to make each request unique for proper Storage
+	// DRS evaluation.
+	vmSpec := types.VirtualMachineConfigSpec{
+		Name:     fmt.Sprintf("packer-placement-request-%d", time.Now().UnixNano()),
+		NumCPUs:  1,
+		MemoryMB: 512,
+		Files: &types.VirtualMachineFileInfo{
+			VmPathName: fmt.Sprintf("[%s]", clusterName),
+		},
+	}
+
+	// Storage DRS requires a resource pool. Return one from the first
+	// datastore's host.
+	var resourcePoolRef *types.ManagedObjectReference
+	if len(datastores) > 0 {
+		dsInfo, err := datastores[0].Info("host")
+		if err == nil && len(dsInfo.Host) > 0 {
+			hostRef := dsInfo.Host[0].Key
+			host := object.NewHostSystem(d.Client.Client, hostRef)
+			hostInfo, err := host.ResourcePool(d.Ctx)
+			if err == nil {
+				ref := hostInfo.Reference()
+				resourcePoolRef = &ref
+			}
+		}
+	}
+
+	placementResult, err := d.RequestStoragePlacement(cluster.Reference(), vmSpec, resourcePoolRef)
+	if err == nil && placementResult != nil && len(placementResult.Recommendations) > 0 {
+		recommendation := placementResult.Recommendations[0]
+
+		if len(recommendation.Action) > 0 {
+			for _, action := range recommendation.Action {
+				if relocateAction, ok := action.(*types.StoragePlacementAction); ok {
+					datastoreObj := object.NewDatastore(d.Client.Client, relocateAction.Destination)
+					dsDriver := &DatastoreDriver{
+						ds:     datastoreObj,
+						driver: d,
+					}
+					info, err := dsDriver.Info("name")
+					if err != nil {
+						log.Printf("[WARN] Failed to get datastore name: %s", err)
+						continue
+					}
+					log.Printf("[INFO] Storage DRS recommended datastore '%s' for cluster '%s'",
+						info.Name, clusterName)
+
+					ds, err := d.Finder.Datastore(d.Ctx, info.Name)
+					if err != nil {
+						log.Printf("[WARN] Failed to find datastore '%s': %s. Using direct reference.", info.Name, err)
+						return dsDriver, SelectionMethodDRS, nil
+					}
+
+					return &DatastoreDriver{
+						ds:     ds,
+						driver: d,
+					}, SelectionMethodDRS, nil
+				}
+			}
+		}
+	}
+
+	if err != nil {
+		log.Printf("[WARN] Storage DRS failed for cluster '%s': %s. Using first-available fallback.", clusterName, err)
+	}
+
+	return datastores[0], SelectionMethodFallback, nil
+}
diff --git a/builder/vsphere/iso/builder.go b/builder/vsphere/iso/builder.go
index 9202852..3e02aba 100644
--- a/builder/vsphere/iso/builder.go
+++ b/builder/vsphere/iso/builder.go
@@ -48,6 +48,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
 		&common.StepConnect{
 			Config: &b.config.ConnectConfig,
 		},
+		&common.StepResolveDatastore{
+			Datastore:        b.config.Datastore,
+			DatastoreCluster: b.config.DatastoreCluster,
+			DiskCount:        len(b.config.StorageConfig.Storage),
+		},
 		&common.StepDownload{
 			DownloadStep: &commonsteps.StepDownload{
 				Checksum:    b.config.ISOChecksum,
diff --git a/builder/vsphere/iso/config.hcl2spec.go b/builder/vsphere/iso/config.hcl2spec.go
index da69b9d..716e004 100644
--- a/builder/vsphere/iso/config.hcl2spec.go
+++ b/builder/vsphere/iso/config.hcl2spec.go
@@ -48,6 +48,7 @@ type FlatConfig struct {
 	Host                            *string                                     `mapstructure:"host" cty:"host" hcl:"host"`
 	ResourcePool                    *string                                     `mapstructure:"resource_pool" cty:"resource_pool" hcl:"resource_pool"`
 	Datastore                       *string                                     `mapstructure:"datastore" cty:"datastore" hcl:"datastore"`
+	DatastoreCluster                *string                                     `mapstructure:"datastore_cluster" cty:"datastore_cluster" hcl:"datastore_cluster"`
 	SetHostForDatastoreUploads      *bool                                       `mapstructure:"set_host_for_datastore_uploads" cty:"set_host_for_datastore_uploads" hcl:"set_host_for_datastore_uploads"`
 	CPUs                            *int32                                      `mapstructure:"CPUs" cty:"CPUs" hcl:"CPUs"`
 	CpuCores                        *int32                                      `mapstructure:"cpu_cores" cty:"cpu_cores" hcl:"cpu_cores"`
@@ -209,6 +210,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
 		"host":                           &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
 		"resource_pool":                  &hcldec.AttrSpec{Name: "resource_pool", Type: cty.String, Required: false},
 		"datastore":                      &hcldec.AttrSpec{Name: "datastore", Type: cty.String, Required: false},
+		"datastore_cluster":              &hcldec.AttrSpec{Name: "datastore_cluster", Type: cty.String, Required: false},
 		"set_host_for_datastore_uploads": &hcldec.AttrSpec{Name: "set_host_for_datastore_uploads", Type: cty.Bool, Required: false},
 		"CPUs":                           &hcldec.AttrSpec{Name: "CPUs", Type: cty.Number, Required: false},
 		"cpu_cores":                      &hcldec.AttrSpec{Name: "cpu_cores", Type: cty.Number, Required: false},
diff --git a/builder/vsphere/iso/step_create.go b/builder/vsphere/iso/step_create.go
index a5128a6..77c0a03 100644
--- a/builder/vsphere/iso/step_create.go
+++ b/builder/vsphere/iso/step_create.go
@@ -9,6 +9,7 @@ package iso
 import (
 	"context"
 	"fmt"
+	"log"
 	"path"
 	"strings"
 
@@ -17,6 +18,7 @@ import (
 	"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common"
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
+	"github.com/vmware/govmomi/vim25/types"
 )
 
 // If no adapter is defined, network tasks (communicators, most provisioners)
@@ -189,8 +191,6 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
 
 	ui.Say("Creating virtual machine...")
 
-	// Add network/network card on the first NIC for backwards compatibility in
-	// the type is defined.
 	var networkCards []driver.NIC
 	for _, nic := range s.Config.NICs {
 		networkCards = append(networkCards, driver.NIC{
@@ -201,8 +201,6 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
 		})
 	}
 
-	// Add disk as the first drive for backwards compatibility if the type is
-	// defined
 	var disks []driver.Disk
 	for _, disk := range s.Config.StorageConfig.Storage {
 		disks = append(disks, driver.Disk{
@@ -213,10 +211,59 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
 		})
 	}
 
+	datastoreName := s.Location.Datastore
+	var primaryDatastore driver.Datastore
+	if ds, ok := state.GetOk("datastore"); ok {
+		primaryDatastore = ds.(driver.Datastore)
+		datastoreName = primaryDatastore.Name()
+	}
+
+	// If no datastore was resolved and no datastore was specified, return an error.
+	if datastoreName == "" && s.Location.DatastoreCluster == "" {
+		state.Put("error", fmt.Errorf("no datastore specified and no datastore resolved from cluster"))
+		return multistep.ActionHalt
+	}
+
+	// Handle multi-disk placement when using a datastore cluster.
+	var datastoreRefs []*types.ManagedObjectReference
+	if s.Location.DatastoreCluster != "" && len(disks) > 1 {
+		if vcDriver, ok := d.(*driver.VCenterDriver); ok {
+			// Request Storage DRS recommendations for all disks at once for optimal placement.
+			ui.Sayf("Requesting Storage DRS recommendations for %d disks...", len(disks))
+
+			diskDatastores, method, err := vcDriver.SelectDatastoresForDisks(s.Location.DatastoreCluster, disks)
+			if err != nil {
+				ui.Errorf("Warning: Failed to get Storage DRS recommendations: %s. Using primary datastore.", err)
+				if primaryDatastore != nil {
+					ref := primaryDatastore.Reference()
+					for i := 0; i < len(disks); i++ {
+						datastoreRefs = append(datastoreRefs, &ref)
+					}
+				}
+			} else {
+				// Use the first disk's datastore as the primary datastore
+				if len(diskDatastores) > 0 {
+					datastoreName = diskDatastores[0].Name()
+				}
+
+				for i, ds := range diskDatastores {
+					ref := ds.Reference()
+					if method == driver.SelectionMethodDRS {
+						log.Printf("[INFO] Disk %d: Storage DRS selected datastore '%s'", i+1, ds.Name())
+					} else {
+						log.Printf("[INFO] Disk %d: Using first available datastore '%s'", i+1, ds.Name())
+					}
+					datastoreRefs = append(datastoreRefs, &ref)
+				}
+			}
+		}
+	}
+
 	vm, err := d.CreateVM(&driver.CreateConfig{
 		StorageConfig: driver.StorageConfig{
 			DiskControllerType: s.Config.StorageConfig.DiskControllerType,
 			Storage:            disks,
+			DatastoreRefs:      datastoreRefs,
 		},
 		Annotation:    s.Config.Notes,
 		Name:          s.Location.VMName,
@@ -224,7 +271,7 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
 		Cluster:       s.Location.Cluster,
 		Host:          s.Location.Host,
 		ResourcePool:  s.Location.ResourcePool,
-		Datastore:     s.Location.Datastore,
+		Datastore:     datastoreName,
 		GuestOS:       s.Config.GuestOSType,
 		NICs:          networkCards,
 		USBController: s.Config.USBController,
diff --git a/docs-partials/builder/vsphere/common/LocationConfig-not-required.mdx b/docs-partials/builder/vsphere/common/LocationConfig-not-required.mdx
index 5ded722..e7e291f 100644
--- a/docs-partials/builder/vsphere/common/LocationConfig-not-required.mdx
+++ b/docs-partials/builder/vsphere/common/LocationConfig-not-required.mdx
@@ -8,8 +8,8 @@
   Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
   section for more details.
 
-- `host` (string) - The ESXi host where the virtual machine is created. A full path must be
-  specified if the ESXi host is in a folder. For example `folder/host`.
+- `host` (string) - The ESX host where the virtual machine is created. A full path must be specified
+  if the ESX host is in a folder. For example `folder/host`.
   Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
   section for more details.
 
@@ -22,9 +22,17 @@
   a nested path might resemble 'rp-packer/rp-linux-images'.
 
 - `datastore` (string) - The datastore where the virtual machine is created.
-  Required if `host` is a cluster, or if `host` has multiple datastores.
+  Required if `host` is a cluster or if `host` has multiple datastores,
+  unless `datastore_cluster` is specified.
+  
+  ~> **Note:** Cannot be used with `datastore_cluster`.
+
+- `datastore_cluster` (string) - The datastore cluster where the virtual machine is created.
+  When specified, Storage DRS will automatically select the optimal datastore.
+  
+  ~> **Note:** Cannot be used with `datastore`.
 
-- `set_host_for_datastore_uploads` (bool) - The ESXI host used for uploading files to the datastore.
+- `set_host_for_datastore_uploads` (bool) - The ESX host used for uploading files to the datastore.
   Defaults to `false`.
 
 <!-- End of code generated from the comments of the LocationConfig struct in builder/vsphere/common/config_location.go; -->
diff --git a/docs-partials/post-processor/vsphere/Config-not-required.mdx b/docs-partials/post-processor/vsphere/Config-not-required.mdx
index cb2c72c..35b413c 100644
--- a/docs-partials/post-processor/vsphere/Config-not-required.mdx
+++ b/docs-partials/post-processor/vsphere/Config-not-required.mdx
@@ -1,5 +1,12 @@
 <!-- Code generated from the comments of the Config struct in post-processor/vsphere/post-processor.go; DO NOT EDIT MANUALLY -->
 
+- `datastore` (string) - The name of the vSphere datastore to place the virtual machine.
+  Mutually exclusive with `datastore_cluster`.
+
+- `datastore_cluster` (string) - The name of the vSphere datastore cluster to place the virtual machine.
+  When specified, Storage DRS will automatically select the optimal datastore.
+  Mutually exclusive with `datastore`.
+
 - `disk_mode` (string) - The disk format of the target virtual machine. One of `thin`, `thick`,
 
 - `esxi_host` (string) - The fully qualified domain name or IP address of the ESX host to upload the
@@ -10,7 +17,8 @@
 - `options` ([]string) - Options to send to `ovftool` when uploading the virtual machine.
   Use `ovftool --help` to list all the options available.
 
-- `overwrite` (bool) - Overwrite existing files. Defaults to `false`.
+- `overwrite` (bool) - Overwrite existing files.
+  If `true`, forces overwrites of existing files. Defaults to `false`.
 
 - `resource_pool` (string) - The name of the resource pool to place the virtual machine.
 
@@ -23,14 +31,15 @@
 
 - `hardware_version` (string) - The maximum virtual hardware version for the deployed virtual machine.
   
-  It does not upgrade the virtual hardware version of the source VM. Instead, it limits the
-  virtual hardware version of the deployed virtual machine  to the specified version.
-  If the source virtual machine's hardware version is higher than the specified version, the
-  deployed virtual machine's hardware version will be downgraded to the specified version.
+  It does not upgrade the virtual hardware version of the source VM. Instead, it
+  limits the virtual hardware version of the deployed virtual machine to the
+  specified version. If the source virtual machine's hardware version is higher
+  than the specified version, the deployed virtual machine's hardware version will
+  be downgraded to the specified version.
   
-  If the source virtual machine's hardware version is lower than or equal to the specified
-  version, the deployed virtual machine's hardware version will be the same as the source
-  virtual machine's.
+  If the source virtual machine's hardware version is lower than or equal to the
+  specified version, the deployed virtual machine's hardware version will be the
+  same as the source virtual machine's.
   
   This option is useful when deploying to vCenter instance or an ESX host whose
   version is different than the one used to create the artifact.
diff --git a/docs-partials/post-processor/vsphere/Config-required.mdx b/docs-partials/post-processor/vsphere/Config-required.mdx
index a00ca4b..146af69 100644
--- a/docs-partials/post-processor/vsphere/Config-required.mdx
+++ b/docs-partials/post-processor/vsphere/Config-required.mdx
@@ -1,14 +1,12 @@
 <!-- Code generated from the comments of the Config struct in post-processor/vsphere/post-processor.go; DO NOT EDIT MANUALLY -->
 
-- `cluster` (string) - The cluster or ESX host to upload the virtual machine.
-  This can be either the name of the vSphere cluster or the fully qualified domain name (FQDN)
-  or IP address of the ESX host.
+- `cluster` (string) - The cluster or ESX host to upload the virtual machine. This can be either the
+  name of the vSphere cluster or the fully qualified domain name (FQDN) or IP
+  address of the ESX host.
 
 - `datacenter` (string) - The name of the vSphere datacenter object to place the virtual machine.
   This is _not required_ if `resource_pool` is specified.
 
-- `datastore` (string) - The name of the vSphere datastore to place the virtual machine.
-
 - `host` (string) - The fully qualified domain name or IP address of the vCenter instance or ESX host.
 
 - `password` (string) - The password to use to authenticate to the vSphere endpoint.
diff --git a/post-processor/vsphere/post-processor.go b/post-processor/vsphere/post-processor.go
index bab4ae6..604f151 100644
--- a/post-processor/vsphere/post-processor.go
+++ b/post-processor/vsphere/post-processor.go
@@ -25,6 +25,7 @@ import (
 	shelllocal "github.com/hashicorp/packer-plugin-sdk/shell-local"
 	"github.com/hashicorp/packer-plugin-sdk/template/config"
 	"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
+	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
 )
 
 const (
@@ -36,8 +37,9 @@ const (
 var ovftool = "ovftool"
 
 var (
-	// Regular expression to validate an RFC1035 hostname from and FQDN or simple hostname.
-	// For example "esxi-01". Requires proper DNS setup and/or correct DNS search domain setting.
+	// Regular expression to validate an RFC1035 hostname from and FQDN or simple
+	// hostname. For example "esxi-01". Requires proper DNS setup and/or correct DNS
+	// search domain setting.
 	hostnameRegex = regexp.MustCompile(`^[[:alnum:]][[:alnum:]\-]{0,61}[[:alnum:]]|[[:alpha:]]$`)
 
 	// Simple regular expression to validate IPv4 values.
@@ -47,15 +49,20 @@ var (
 
 type Config struct {
 	common.PackerConfig `mapstructure:",squash"`
-	// The cluster or ESX host to upload the virtual machine.
-	// This can be either the name of the vSphere cluster or the fully qualified domain name (FQDN)
-	// or IP address of the ESX host.
+	// The cluster or ESX host to upload the virtual machine. This can be either the
+	// name of the vSphere cluster or the fully qualified domain name (FQDN) or IP
+	// address of the ESX host.
 	Cluster string `mapstructure:"cluster" required:"true"`
 	// The name of the vSphere datacenter object to place the virtual machine.
 	// This is _not required_ if `resource_pool` is specified.
 	Datacenter string `mapstructure:"datacenter" required:"true"`
 	// The name of the vSphere datastore to place the virtual machine.
-	Datastore string `mapstructure:"datastore"  required:"true"`
+	// Mutually exclusive with `datastore_cluster`.
+	Datastore string `mapstructure:"datastore"`
+	// The name of the vSphere datastore cluster to place the virtual machine.
+	// When specified, Storage DRS will automatically select the optimal datastore.
+	// Mutually exclusive with `datastore`.
+	DatastoreCluster string `mapstructure:"datastore_cluster"`
 	// The disk format of the target virtual machine. One of `thin`, `thick`,
 	DiskMode string `mapstructure:"disk_mode"`
 	// The fully qualified domain name or IP address of the vCenter instance or ESX host.
@@ -68,7 +75,8 @@ type Config struct {
 	// Options to send to `ovftool` when uploading the virtual machine.
 	// Use `ovftool --help` to list all the options available.
 	Options []string `mapstructure:"options"`
-	// Overwrite existing files. Defaults to `false`.
+	// Overwrite existing files.
+	// If `true`, forces overwrites of existing files. Defaults to `false`.
 	Overwrite bool `mapstructure:"overwrite"`
 	// The password to use to authenticate to the vSphere endpoint.
 	Password string `mapstructure:"password" required:"true"`
@@ -85,14 +93,15 @@ type Config struct {
 	VMNetwork string `mapstructure:"vm_network"`
 	// The maximum virtual hardware version for the deployed virtual machine.
 	//
-	// It does not upgrade the virtual hardware version of the source VM. Instead, it limits the
-	// virtual hardware version of the deployed virtual machine  to the specified version.
-	// If the source virtual machine's hardware version is higher than the specified version, the
-	// deployed virtual machine's hardware version will be downgraded to the specified version.
+	// It does not upgrade the virtual hardware version of the source VM. Instead, it
+	// limits the virtual hardware version of the deployed virtual machine to the
+	// specified version. If the source virtual machine's hardware version is higher
+	// than the specified version, the deployed virtual machine's hardware version will
+	// be downgraded to the specified version.
 	//
-	// If the source virtual machine's hardware version is lower than or equal to the specified
-	// version, the deployed virtual machine's hardware version will be the same as the source
-	// virtual machine's.
+	// If the source virtual machine's hardware version is lower than or equal to the
+	// specified version, the deployed virtual machine's hardware version will be the
+	// same as the source virtual machine's.
 	//
 	// This option is useful when deploying to vCenter instance or an ESX host whose
 	// version is different than the one used to create the artifact.
@@ -108,7 +117,8 @@ type Config struct {
 }
 
 type PostProcessor struct {
-	config Config
+	config            Config
+	resolvedDatastore string
 }
 
 func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
@@ -136,7 +146,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
 		p.config.DiskMode = DefaultDiskMode
 	}
 
-	// Accumulate any errors.
+	// Accumulate any errors
 	errs := new(packersdk.MultiError)
 
 	if runtime.GOOS == "windows" {
@@ -148,7 +158,16 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
 			errs, fmt.Errorf("ovftool not found: %s", err))
 	}
 
-	// Define the parameters that are required.
+	if p.config.Datastore != "" && p.config.DatastoreCluster != "" {
+		errs = packersdk.MultiErrorAppend(
+			errs, fmt.Errorf("'datastore' and 'datastore_cluster' are mutually exclusive; specify only one"))
+	}
+
+	if p.config.Datastore == "" && p.config.DatastoreCluster == "" {
+		errs = packersdk.MultiErrorAppend(
+			errs, fmt.Errorf("either 'datastore' or 'datastore_cluster' must be specified"))
+	}
+
 	templates := map[string]*string{
 		"cluster":    &p.config.Cluster,
 		"datacenter": &p.config.Datacenter,
@@ -158,6 +177,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
 		"username":   &p.config.Username,
 		"vm_name":    &p.config.VMName,
 	}
+
 	for key, ptr := range templates {
 		if *ptr == "" {
 			errs = packersdk.MultiErrorAppend(
@@ -173,7 +193,6 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
 }
 
 func (p *PostProcessor) generateURI() (*url.URL, error) {
-	// Use the net/url standard library to encode and escape the URI.
 	ovftoolURI := fmt.Sprintf("vi://%s/%s/host/%s",
 		p.config.Host,
 		p.config.Datacenter,
@@ -202,7 +221,6 @@ func (p *PostProcessor) generateURI() (*url.URL, error) {
 }
 
 func getEncodedPassword(u *url.URL) (string, bool) {
-	// Filter the password from the logs.
 	password, passwordSet := u.User.Password()
 	if passwordSet && password != "" {
 		encodedPassword := strings.Split(u.User.String(), ":")[1]
@@ -224,6 +242,44 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa
 		return nil, false, false, fmt.Errorf("error locating expected .vmx, .ovf, or .ova artifact")
 	}
 
+	if p.config.DatastoreCluster != "" {
+		ui.Say(fmt.Sprintf("Resolving datastore from cluster '%s'...", p.config.DatastoreCluster))
+
+		d, err := p.connectToVCenter()
+		if err != nil {
+			return nil, false, false, fmt.Errorf("error connecting to vCenter: %s", err)
+		}
+		defer func() {
+			if restErr, soapErr := d.Cleanup(); restErr != nil || soapErr != nil {
+				if restErr != nil {
+					ui.Error(fmt.Sprintf("error cleaning up REST client: %s", restErr))
+				}
+				if soapErr != nil {
+					ui.Error(fmt.Sprintf("error cleaning up SOAP client: %s", soapErr))
+				}
+			}
+		}()
+
+		vcenterDriver, ok := d.(*driver.VCenterDriver)
+		if !ok {
+			return nil, false, false, fmt.Errorf("error: driver is not a VCenterDriver")
+		}
+
+		ds, selectionMethod, err := vcenterDriver.SelectDatastoreFromCluster(p.config.DatastoreCluster)
+		if err != nil {
+			return nil, false, false, fmt.Errorf("error selecting datastore from cluster: %s", err)
+		}
+
+		p.resolvedDatastore = ds.Name()
+		if selectionMethod == driver.SelectionMethodDRS {
+			log.Printf("[INFO] Storage DRS selected datastore '%s' from cluster '%s'", p.resolvedDatastore, p.config.DatastoreCluster)
+		} else {
+			log.Printf("[INFO] Selected datastore '%s' from cluster '%s' (first available)", p.resolvedDatastore, p.config.DatastoreCluster)
+		}
+	} else {
+		p.resolvedDatastore = p.config.Datastore
+	}
+
 	ovftoolURI, err := p.generateURI()
 	if err != nil {
 		return nil, false, false, err
@@ -246,7 +302,6 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa
 		return nil, false, false, err
 	}
 
-	// Validation has passed, so run for real.
 	ui.Say("Uploading virtual machine...")
 	commandAndArgs := []string{ovftool}
 	commandAndArgs = append(commandAndArgs, args...)
@@ -269,11 +324,28 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa
 		return nil
 	})
 
-	artifact = NewArtifact(p.config.Datastore, p.config.VMFolder, p.config.VMName, artifact.Files())
+	artifact = NewArtifact(p.resolvedDatastore, p.config.VMFolder, p.config.VMName, artifact.Files())
 
 	return artifact, false, false, nil
 }
 
+func (p *PostProcessor) connectToVCenter() (driver.Driver, error) {
+	connectConfig := &driver.ConnectConfig{
+		VCenterServer:      p.config.Host,
+		Username:           p.config.Username,
+		Password:           p.config.Password,
+		InsecureConnection: p.config.Insecure,
+		Datacenter:         p.config.Datacenter,
+	}
+
+	d, err := driver.NewDriver(connectConfig)
+	if err != nil {
+		return nil, fmt.Errorf("error creating driver: %s", err)
+	}
+
+	return d, nil
+}
+
 func (p *PostProcessor) ValidateOvfTool(args []string, ovftool string, ui packersdk.Ui) error {
 	args = append([]string{"--verifyOnly"}, args...)
 	if p.config.Insecure {
@@ -286,8 +358,8 @@ func (p *PostProcessor) ValidateOvfTool(args []string, ovftool string, ui packer
 	cmd := exec.CommandContext(cmdCtx, ovftool, args...)
 	cmd.Stdout = &out
 
-	// Need to manually close stdin or else the ovftool call will hang if the
-	// user has provided an invalid credential.
+	// Manually closing stdin is necessary to prevent the ovftool call from hanging
+	// indefinitely when the user provides an invalid password or username.
 	stdin, err := cmd.StdinPipe()
 	if err != nil {
 		return err
@@ -318,7 +390,7 @@ func (p *PostProcessor) BuildArgs(source, ovftoolURI string) ([]string, error) {
 	args := []string{
 		"--acceptAllEulas",
 		fmt.Sprintf(`--name=%s`, p.config.VMName),
-		fmt.Sprintf(`--datastore=%s`, p.config.Datastore),
+		fmt.Sprintf(`--datastore=%s`, p.resolvedDatastore),
 	}
 
 	if p.config.Insecure {
diff --git a/post-processor/vsphere/post-processor.hcl2spec.go b/post-processor/vsphere/post-processor.hcl2spec.go
index 21d99c1..cd1a0cd 100644
--- a/post-processor/vsphere/post-processor.hcl2spec.go
+++ b/post-processor/vsphere/post-processor.hcl2spec.go
@@ -20,7 +20,8 @@ type FlatConfig struct {
 	PackerSensitiveVars []string          `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
 	Cluster             *string           `mapstructure:"cluster" required:"true" cty:"cluster" hcl:"cluster"`
 	Datacenter          *string           `mapstructure:"datacenter" required:"true" cty:"datacenter" hcl:"datacenter"`
-	Datastore           *string           `mapstructure:"datastore" required:"true" cty:"datastore" hcl:"datastore"`
+	Datastore           *string           `mapstructure:"datastore" cty:"datastore" hcl:"datastore"`
+	DatastoreCluster    *string           `mapstructure:"datastore_cluster" cty:"datastore_cluster" hcl:"datastore_cluster"`
 	DiskMode            *string           `mapstructure:"disk_mode" cty:"disk_mode" hcl:"disk_mode"`
 	Host                *string           `mapstructure:"host" required:"true" cty:"host" hcl:"host"`
 	ESXHost             *string           `mapstructure:"esxi_host" cty:"esxi_host" hcl:"esxi_host"`
@@ -60,6 +61,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
 		"cluster":                    &hcldec.AttrSpec{Name: "cluster", Type: cty.String, Required: false},
 		"datacenter":                 &hcldec.AttrSpec{Name: "datacenter", Type: cty.String, Required: false},
 		"datastore":                  &hcldec.AttrSpec{Name: "datastore", Type: cty.String, Required: false},
+		"datastore_cluster":          &hcldec.AttrSpec{Name: "datastore_cluster", Type: cty.String, Required: false},
 		"disk_mode":                  &hcldec.AttrSpec{Name: "disk_mode", Type: cty.String, Required: false},
 		"host":                       &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
 		"esxi_host":                  &hcldec.AttrSpec{Name: "esxi_host", Type: cty.String, Required: false},



Test patch:
diff --git a/builder/vsphere/common/step_resolve_datastore_test.go b/builder/vsphere/common/step_resolve_datastore_test.go
new file mode 100644
index 0000000..1e51a26
--- /dev/null
+++ b/builder/vsphere/common/step_resolve_datastore_test.go
@@ -0,0 +1,190 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package common
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/hashicorp/packer-plugin-sdk/multistep"
+	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
+)
+
+func TestStepResolveDatastore_Run(t *testing.T) {
+	tc := []struct {
+		name              string
+		step              *StepResolveDatastore
+		driverMock        *VCenterDriverMock
+		expectedAction    multistep.StepAction
+		expectedDatastore string
+		expectedMethod    string
+		expectError       bool
+		errorContains     string
+	}{
+		{
+			name: "Resolve from direct datastore",
+			step: &StepResolveDatastore{
+				Datastore: "test-datastore",
+			},
+			driverMock: func() *VCenterDriverMock {
+				m := NewVCenterDriverMock()
+				m.DatastoreMock = &driver.DatastoreMock{
+					NameReturn: "test-datastore",
+				}
+				return m
+			}(),
+			expectedAction:    multistep.ActionContinue,
+			expectedDatastore: "test-datastore",
+			expectedMethod:    "direct",
+			expectError:       false,
+		},
+		{
+			name: "Resolve from datastore cluster with DRS",
+			step: &StepResolveDatastore{
+				DatastoreCluster: "test-cluster",
+			},
+			driverMock: func() *VCenterDriverMock {
+				m := NewVCenterDriverMock()
+				m.SelectDatastoreReturn = &driver.DatastoreMock{
+					NameReturn: "cluster-datastore-1",
+				}
+				m.SelectDatastoreMethod = driver.SelectionMethodDRS
+				return m
+			}(),
+			expectedAction:    multistep.ActionContinue,
+			expectedDatastore: "cluster-datastore-1",
+			expectedMethod:    driver.SelectionMethodDRS,
+			expectError:       false,
+		},
+		{
+			name: "Resolve from datastore cluster with fallback",
+			step: &StepResolveDatastore{
+				DatastoreCluster: "test-cluster",
+			},
+			driverMock: func() *VCenterDriverMock {
+				m := NewVCenterDriverMock()
+				m.SelectDatastoreReturn = &driver.DatastoreMock{
+					NameReturn: "cluster-datastore-1",
+				}
+				m.SelectDatastoreMethod = driver.SelectionMethodFallback
+				return m
+			}(),
+			expectedAction:    multistep.ActionContinue,
+			expectedDatastore: "cluster-datastore-1",
+			expectedMethod:    driver.SelectionMethodFallback,
+			expectError:       false,
+		},
+		{
+			name: "Error finding direct datastore",
+			step: &StepResolveDatastore{
+				Datastore: "missing-datastore",
+			},
+			driverMock: func() *VCenterDriverMock {
+				m := NewVCenterDriverMock()
+				m.FindDatastoreErr = fmt.Errorf("datastore not found")
+				return m
+			}(),
+			expectedAction: multistep.ActionHalt,
+			expectError:    true,
+			errorContains:  "error finding datastore 'missing-datastore'",
+		},
+		{
+			name: "Error selecting from datastore cluster",
+			step: &StepResolveDatastore{
+				DatastoreCluster: "missing-cluster",
+			},
+			driverMock: func() *VCenterDriverMock {
+				m := NewVCenterDriverMock()
+				m.SelectDatastoreErr = fmt.Errorf("cluster not found")
+				return m
+			}(),
+			expectedAction: multistep.ActionHalt,
+			expectError:    true,
+			errorContains:  "error resolving datastore from cluster 'missing-cluster'",
+		},
+	}
+
+	for _, c := range tc {
+		t.Run(c.name, func(t *testing.T) {
+			state := basicStateBag(nil)
+			state.Put("driver", c.driverMock)
+
+			action := c.step.Run(context.TODO(), state)
+			if action != c.expectedAction {
+				t.Fatalf("unexpected action: expected '%#v', but returned '%#v'", c.expectedAction, action)
+			}
+
+			if c.expectError {
+				err, ok := state.Get("error").(error)
+				if !ok {
+					t.Fatal("expected error in state bag, but none found")
+				}
+				if !strings.Contains(err.Error(), c.errorContains) {
+					t.Fatalf("unexpected error: expected to contain '%s', but got '%s'", c.errorContains, err.Error())
+				}
+			} else {
+				if _, ok := state.GetOk("error"); ok {
+					t.Fatal("unexpected error in state bag")
+				}
+
+				ds, ok := state.Get("datastore").(driver.Datastore)
+				if !ok {
+					t.Fatal("expected datastore in state bag, but none found")
+				}
+				if ds.Name() != c.expectedDatastore {
+					t.Fatalf("unexpected datastore: expected '%s', but got '%s'", c.expectedDatastore, ds.Name())
+				}
+
+				method, ok := state.Get("datastore_selection_method").(string)
+				if !ok {
+					t.Fatal("expected datastore_selection_method in state bag, but none found")
+				}
+				if method != c.expectedMethod {
+					t.Fatalf("unexpected selection method: expected '%s', but got '%s'", c.expectedMethod, method)
+				}
+			}
+
+			// Verify mock was called correctly
+			if c.step.Datastore != "" && !c.driverMock.FindDatastoreCalled {
+				t.Fatal("expected FindDatastore to be called, but it wasn't")
+			}
+			if c.step.DatastoreCluster != "" && !c.driverMock.SelectDatastoreCalled {
+				t.Fatal("expected SelectDatastoreFromCluster to be called, but it wasn't")
+			}
+		})
+	}
+}
+
+func TestStepResolveDatastore_Cleanup(t *testing.T) {
+	step := &StepResolveDatastore{}
+	state := basicStateBag(nil)
+
+	// Cleanup should be a no-op
+	step.Cleanup(state)
+}
+
+// VCenterDriverMock embeds DriverMock and adds VCenterDriver-specific methods for testing
+type VCenterDriverMock struct {
+	*driver.DriverMock
+
+	SelectDatastoreCalled bool
+	SelectDatastoreReturn driver.Datastore
+	SelectDatastoreMethod string
+	SelectDatastoreErr    error
+}
+
+// NewVCenterDriverMock creates a new VCenterDriverMock
+func NewVCenterDriverMock() *VCenterDriverMock {
+	return &VCenterDriverMock{
+		DriverMock: driver.NewDriverMock(),
+	}
+}
+
+// SelectDatastoreFromCluster mocks the VCenterDriver method
+func (d *VCenterDriverMock) SelectDatastoreFromCluster(clusterName string) (driver.Datastore, string, error) {
+	d.SelectDatastoreCalled = true
+	return d.SelectDatastoreReturn, d.SelectDatastoreMethod, d.SelectDatastoreErr
+}
diff --git a/post-processor/vsphere/post-processor_test.go b/post-processor/vsphere/post-processor_test.go
index d1e491b..0da2bb5 100644
--- a/post-processor/vsphere/post-processor_test.go
+++ b/post-processor/vsphere/post-processor_test.go
@@ -29,6 +29,7 @@ func TestArgs(t *testing.T) {
 	var p PostProcessor
 
 	p.config = getTestConfig()
+	p.resolvedDatastore = p.config.Datastore
 
 	source := "something.vmx"
 	ovftoolURI := fmt.Sprintf("vi://%s:%s@%s/%s/host/%s",