Repository: kurtosis-tech/kurtosis
Pull request #2025 (2024-01-08 13:31:22)

feat!: allow to mount multiple artifacts to the same folder in a service. Users will need to replace the `Directory.artifac_name` field key with `Directory.artifac_names`
## Description:
allow to mount multiple artifacts to the same folder in a service

## Is this change user facing?
YES

## References (if applicable):
Fix https://github.com/kurtosis-tech/kurtosis/issues/1519


cloudflare-pages[bot]: ## Deploying with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>83f1daf</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://408ef86f.kurtosis-docs.pages.dev'>https://408ef86f.kurtosis-docs.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://lporoli-ticket-1519.kurtosis-docs.pages.dev'>https://lporoli-ticket-1519.kurtosis-docs.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/kurtosis/408ef86f-6ad6-4253-a205-3acfc67b019d)


Patch:
diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go
index 8b8e50ed0..d2ede4273 100644
--- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go
+++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go
@@ -15,6 +15,9 @@ const (
 	//  be stored in the port spec label
 	KurtosisInternalContainerGrpcPortId = "grpc"
 
+	// The ID of the REST API port.
+	KurtosisInternalContainerRESTAPIPortId = "rest-api"
+
 	// The engine server uses gRPC so MUST listen on TCP (no other protocols are supported)
 	EngineTransportProtocol = port_spec.TransportProtocol_TCP
 
diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go
index eca92e210..3f33d55e4 100644
--- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go
+++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go
@@ -173,6 +173,8 @@ func CreateEngine(
 		engineGuid,
 		consts.KurtosisInternalContainerGrpcPortId,
 		privateGrpcPortSpec,
+		consts.KurtosisInternalContainerRESTAPIPortId,
+		restAPIPortSpec,
 	)
 	if err != nil {
 		return nil, stacktrace.Propagate(
diff --git a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go
index 8637f16cf..4a0d99f6c 100644
--- a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go
+++ b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go
@@ -1738,7 +1738,7 @@ func (manager *DockerManager) getContainerHostConfig(
 	}
 
 	// NOTE: Do NOT use PublishAllPorts here!!!! This will work if a Dockerfile doesn't have an EXPOSE directive, but
-	//  if the Dockerfile *does* have an EXPOSE directive then _only_ the ports with EXPOSE will be published
+	//  if the Dockerfile *does* have and EXPOSE directive then _only_ the ports with EXPOSE will be published
 	// See also: https://www.ctl.io/developers/blog/post/docker-networking-rules/
 
 	containerHostConfigPtr := &container.HostConfig{
diff --git a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/enclave_object_attributes_provider.go b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/enclave_object_attributes_provider.go
index 549dff52c..0271988cf 100644
--- a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/enclave_object_attributes_provider.go
+++ b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/enclave_object_attributes_provider.go
@@ -541,6 +541,7 @@ func (provider *dockerEnclaveObjectAttributesProviderImpl) getLabelsForEnclaveOb
 //	"traefik.http.services.65d2fb6d6732-3771c85af16a-81.loadbalancer.server.port": "81"
 func (provider *dockerEnclaveObjectAttributesProviderImpl) getTraefikLabelsForEnclaveObject(serviceUuid string, ports map[string]*port_spec.PortSpec) (map[*docker_label_key.DockerLabelKey]*docker_label_value.DockerLabelValue, error) {
 	labels := map[*docker_label_key.DockerLabelKey]*docker_label_value.DockerLabelValue{}
+	labelKeyValuePairs := map[string]string{}
 
 	for _, portSpec := range ports {
 		maybeApplicationProtocol := ""
@@ -552,59 +553,26 @@ func (provider *dockerEnclaveObjectAttributesProviderImpl) getTraefikLabelsForEn
 			shortServiceUuid := uuid_generator.ShortenedUUIDString(serviceUuid)
 			servicePortStr := fmt.Sprintf("%s-%s-%d", shortEnclaveUuid, shortServiceUuid, portSpec.GetNumber())
 
-			// Header Host rule
-			ruleKeySuffix := fmt.Sprintf("http.routers.%s.rule", servicePortStr)
-			ruleLabelKey, err := docker_label_key.CreateNewDockerTraefikLabelKey(ruleKeySuffix)
-			if err != nil {
-				return nil, stacktrace.Propagate(err, "An error occurred getting the traefik rule label key with suffix '%v'", ruleKeySuffix)
-			}
-			ruleValue := fmt.Sprintf("Host(`%d-%s-%s`)", portSpec.GetNumber(), shortServiceUuid, shortEnclaveUuid)
-			ruleLabelValue, err := docker_label_value.CreateNewDockerLabelValue(ruleValue)
-			if err != nil {
-				return nil, stacktrace.Propagate(err, "An error occurred creating the traefik rule label value with value '%v'", ruleValue)
-			}
-			labels[ruleLabelKey] = ruleLabelValue
-
-			// Service name
-			serviceKeySuffix := fmt.Sprintf("http.routers.%s.service", servicePortStr)
-			serviceLabelKey, err := docker_label_key.CreateNewDockerTraefikLabelKey(serviceKeySuffix)
-			if err != nil {
-				return nil, stacktrace.Propagate(err, "An error occurred getting the traefik service label key with suffix '%v'", serviceKeySuffix)
-			}
-			serviceValue := servicePortStr
-			serviceLabelValue, err := docker_label_value.CreateNewDockerLabelValue(serviceValue)
-			if err != nil {
-				return nil, stacktrace.Propagate(err, "An error occurred creating the traefik service label value with value '%v'", serviceValue)
-			}
-			labels[serviceLabelKey] = serviceLabelValue
-
-			// Service port number
-			portKeySuffix := fmt.Sprintf("http.services.%s.loadbalancer.server.port", servicePortStr)
-			portLabelKey, err := docker_label_key.CreateNewDockerTraefikLabelKey(portKeySuffix)
-			if err != nil {
-				return nil, stacktrace.Propagate(err, "An error occurred getting the traefik port label key with suffix '%v'", portKeySuffix)
-			}
-			portValue := strconv.Itoa(int(portSpec.GetNumber()))
-			portLabelValue, err := docker_label_value.CreateNewDockerLabelValue(portValue)
-			if err != nil {
-				return nil, stacktrace.Propagate(err, "An error occurred creating the traefik port label value with value '%v'", portValue)
-			}
-			labels[portLabelKey] = portLabelValue
+			labelKeyValuePairs[fmt.Sprintf("http.routers.%s.rule", servicePortStr)] = fmt.Sprintf("Host(`%d-%s-%s`)", portSpec.GetNumber(), shortServiceUuid, shortEnclaveUuid)
+			labelKeyValuePairs[fmt.Sprintf("http.routers.%s.service", servicePortStr)] = servicePortStr
+			labelKeyValuePairs[fmt.Sprintf("http.services.%s.loadbalancer.server.port", servicePortStr)] = strconv.Itoa(int(portSpec.GetNumber()))
 		}
 	}
 
-	if len(labels) > 0 {
-		// Enable Traefik for this service is there is at least one traefik label
-		traefikEnableLabelKey, err := docker_label_key.CreateNewDockerTraefikLabelKey("enable")
+	if len(labelKeyValuePairs) > 0 {
+		labelKeyValuePairs["enable"] = "true"
+	}
+
+	for key, value := range labelKeyValuePairs {
+		labelKey, err := docker_label_key.CreateNewDockerTraefikLabelKey(key)
 		if err != nil {
-			return nil, stacktrace.Propagate(err, "An error occurred getting the traefik enable label key")
+			return nil, stacktrace.Propagate(err, "An error occurred getting the traefik  label key with suffix '%v'", key)
 		}
-		traefikEnableValue := "true"
-		traefikEnableLabelValue, err := docker_label_value.CreateNewDockerLabelValue(traefikEnableValue)
+		labelValue, err := docker_label_value.CreateNewDockerLabelValue(value)
 		if err != nil {
-			return nil, stacktrace.Propagate(err, "An error occurred creating the traefik enable label value with value '%v'", traefikEnableValue)
+			return nil, stacktrace.Propagate(err, "An error occurred creating the traefik  label value with value '%v'", value)
 		}
-		labels[traefikEnableLabelKey] = traefikEnableLabelValue
+		labels[labelKey] = labelValue
 	}
 
 	return labels, nil
diff --git a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go
index 29452431a..a0691ad4e 100644
--- a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go
+++ b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go
@@ -1,6 +1,8 @@
 package object_attributes_provider
 
 import (
+	"fmt"
+	"strconv"
 	"strings"
 
 	"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/docker_label_key"
@@ -18,6 +20,7 @@ const (
 	engineServerNamePrefix = "kurtosis-engine"
 	logsAggregatorName     = "kurtosis-logs-aggregator"
 	logsStorageVolumeName  = "kurtosis-logs-storage"
+	engineRESTAPIPortStr   = "engine-rest-api"
 	reverseProxyNamePrefix = "kurtosis-reverse-proxy"
 )
 
@@ -26,6 +29,8 @@ type DockerObjectAttributesProvider interface {
 		guid engine.EngineGUID,
 		grpcPortId string,
 		grpcPortSpec *port_spec.PortSpec,
+		restAPIPortId string,
+		restAPIPortSpec *port_spec.PortSpec,
 	) (DockerObjectAttributes, error)
 	ForEnclave(enclaveUuid enclave.EnclaveUUID) (DockerEnclaveObjectAttributesProvider, error)
 	ForLogsAggregator() (DockerObjectAttributes, error)
@@ -48,6 +53,8 @@ func (provider *dockerObjectAttributesProviderImpl) ForEngineServer(
 	guid engine.EngineGUID,
 	grpcPortId string,
 	grpcPortSpec *port_spec.PortSpec,
+	restAPIPortId string,
+	restAPIPortSpec *port_spec.PortSpec,
 ) (DockerObjectAttributes, error) {
 
 	nameStr := strings.Join(
@@ -86,6 +93,14 @@ func (provider *dockerObjectAttributesProviderImpl) ForEngineServer(
 		docker_label_key.GUIDDockerLabelKey:          guidLabelValue,
 	}
 
+	traefikLabels, err := provider.getTraefikLabelsForEngine(restAPIPortSpec)
+	if err != nil {
+		return nil, stacktrace.Propagate(err, "An error occurred getting traefik labels for engine server")
+	}
+	for traefikLabelKey, traefikLabelValue := range traefikLabels {
+		labels[traefikLabelKey] = traefikLabelValue
+	}
+
 	objectAttributes, err := newDockerObjectAttributesImpl(name, labels)
 	if err != nil {
 		return nil, stacktrace.Propagate(err, "An error occurred while creating the ObjectAttributesImpl with the name '%s' and labels '%+v'", name, labels)
@@ -173,3 +188,34 @@ func (provider *dockerObjectAttributesProviderImpl) ForReverseProxy(engineGuid e
 	}
 	return objectAttributes, nil
 }
+
+// Return Traefik labels
+// Including the labels required to route traffic to the engine rest api port if the Host header is set to "engine".
+//
+//	"traefik.enable": "true",
+//	"traefik.http.routers.engine-rest-api.rule": "Host(`engine`)",
+//	"traefik.http.routers.engine-rest-api.service": "engine-rest-api",
+//	"traefik.http.services.engine-rest-api.loadbalancer.server.port": "<engine rest api port number>"
+func (provider *dockerObjectAttributesProviderImpl) getTraefikLabelsForEngine(restAPIPortSpec *port_spec.PortSpec) (map[*docker_label_key.DockerLabelKey]*docker_label_value.DockerLabelValue, error) {
+	labels := map[*docker_label_key.DockerLabelKey]*docker_label_value.DockerLabelValue{}
+	labelKeyValuePairs := map[string]string{
+		fmt.Sprintf("http.routers.%s.rule", engineRESTAPIPortStr):                      fmt.Sprintf("Host(`%s`)", engine.RESTAPIPortHostHeader),
+		fmt.Sprintf("http.routers.%s.service", engineRESTAPIPortStr):                   engineRESTAPIPortStr,
+		fmt.Sprintf("http.services.%s.loadbalancer.server.port", engineRESTAPIPortStr): strconv.Itoa(int(restAPIPortSpec.GetNumber())),
+		"enable": "true",
+	}
+
+	for key, value := range labelKeyValuePairs {
+		labelKey, err := docker_label_key.CreateNewDockerTraefikLabelKey(key)
+		if err != nil {
+			return nil, stacktrace.Propagate(err, "An error occurred getting the traefik  label key with suffix '%v'", key)
+		}
+		labelValue, err := docker_label_value.CreateNewDockerLabelValue(value)
+		if err != nil {
+			return nil, stacktrace.Propagate(err, "An error occurred creating the traefik  label value with value '%v'", value)
+		}
+		labels[labelKey] = labelValue
+	}
+
+	return labels, nil
+}
diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go
index 937b9d2e1..ebafaf1d8 100644
--- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go
+++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go
@@ -28,8 +28,6 @@ const (
 	maxWaitForEngineContainerAvailabilityRetries         = 30
 	timeBetweenWaitForEngineContainerAvailabilityRetries = 1 * time.Second
 	httpApplicationProtocol                              = "http"
-
-	restAPIPortHost = "engine"
 )
 
 var noWait *port_spec.Wait = nil
@@ -584,7 +582,7 @@ func getEngineIngressRules(
 ) ([]netv1.IngressRule, error) {
 	var ingressRules []netv1.IngressRule
 	ingressRule := netv1.IngressRule{
-		Host: restAPIPortHost,
+		Host: engine.RESTAPIPortHostHeader,
 		IngressRuleValue: netv1.IngressRuleValue{
 			HTTP: &netv1.HTTPIngressRuleValue{
 				Paths: []netv1.HTTPIngressPath{
diff --git a/container-engine-lib/lib/backend_interface/objects/engine/consts.go b/container-engine-lib/lib/backend_interface/objects/engine/consts.go
index 91c163399..226ad7225 100644
--- a/container-engine-lib/lib/backend_interface/objects/engine/consts.go
+++ b/container-engine-lib/lib/backend_interface/objects/engine/consts.go
@@ -1,6 +1,7 @@
 package engine
 
 const (
-	RESTAPIPortAddr uint16 = 9779
-	RESTAPIHostIP   string = "0.0.0.0"
+	RESTAPIPortAddr       uint16 = 9779
+	RESTAPIHostIP         string = "0.0.0.0"
+	RESTAPIPortHostHeader string = "engine"
 )
diff --git a/container-engine-lib/lib/backend_interface/objects/service_directory/files_artifacts_expansion.go b/container-engine-lib/lib/backend_interface/objects/service_directory/files_artifacts_expansion.go
index 7d92e85fb..b436b12b9 100644
--- a/container-engine-lib/lib/backend_interface/objects/service_directory/files_artifacts_expansion.go
+++ b/container-engine-lib/lib/backend_interface/objects/service_directory/files_artifacts_expansion.go
@@ -9,8 +9,8 @@ type FilesArtifactsExpansion struct {
 	// its operation
 	ExpanderEnvVars map[string]string
 
-	// Map of dirpaths on the target service to the artificat identifier that will be mounted at this location
-	ServiceDirpathsToArtifactIdentifiers map[string]string
+	// Map of dirpaths on the target service to the artifact identifier that will be mounted at this location
+	ServiceDirpathsToArtifactIdentifiers map[string][]string
 
 	// Map of dirpaths that the expander container expects (which the expander will expand into), mapped to
 	// dirpaths on the user service container where those same directories should be made available
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go
index 4a1afa5bd..f948d3ce7 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go
@@ -209,10 +209,12 @@ func (builtin *AddServiceCapabilities) TryResolveWith(instructionsAreEqual bool,
 	// We check if there has been some updates to the files it's mounting. If that's the case, it should be rerun
 	filesArtifactsExpansion := builtin.serviceConfig.GetFilesArtifactsExpansion()
 	if filesArtifactsExpansion != nil {
-		for _, filesArtifactName := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
-			if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
-				enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsUpdated)
-				return enclave_structure.InstructionIsUpdate
+		for _, filesArtifactNames := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
+			for _, filesArtifactName := range filesArtifactNames {
+				if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
+					enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsUpdated)
+					return enclave_structure.InstructionIsUpdate
+				}
 			}
 		}
 	}
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go
index 70d40974e..5b35126d8 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go
@@ -87,9 +87,11 @@ func validateSingleService(validatorEnvironment *startosis_validator.ValidatorEn
 		return startosis_errors.NewValidationError("There was an error validating '%s' as service with the name '%s' already exists inside the package. Adding two different services with the same name isn't allowed; we recommend prefixing/suffixing the two service names or using two different names entirely.", AddServiceBuiltinName, serviceName)
 	}
 	if serviceConfig.GetFilesArtifactsExpansion() != nil {
-		for _, artifactName := range serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers {
-			if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
-				return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", AddServiceBuiltinName, artifactName)
+		for _, artifactNames := range serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers {
+			for _, artifactName := range artifactNames {
+				if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
+					return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", AddServiceBuiltinName, artifactName)
+				}
 			}
 		}
 	}
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go
index 25bc9ac01..f5c2f372c 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go
@@ -259,9 +259,11 @@ func (builtin *AddServicesCapabilities) TryResolveWith(instructionsAreEqual bool
 		// Check whether one file as been updated - if yes the instruction will need to be rerun
 		filesArtifactsExpansion := serviceConfig.GetFilesArtifactsExpansion()
 		if filesArtifactsExpansion != nil {
-			for _, filesArtifactName := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
-				if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
-					atLeastOneFileHasBeenUpdated = true
+			for _, filesArtifactNames := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
+				for _, filesArtifactName := range filesArtifactNames {
+					if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
+						atLeastOneFileHasBeenUpdated = true
+					}
 				}
 			}
 		}
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go
index b7877b031..04f982e1c 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go
@@ -192,7 +192,11 @@ func (builtin *RunPythonCapabilities) Interpret(_ string, arguments *builtin_arg
 			if interpretationErr != nil {
 				return nil, interpretationErr
 			}
-			filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(filesArtifactMountDirPaths, builtin.serviceNetwork)
+			multipleFilesArtifactsMountDirPaths := map[string][]string{}
+			for pathToFile, fileArtifactName := range filesArtifactMountDirPaths {
+				multipleFilesArtifactsMountDirPaths[pathToFile] = []string{fileArtifactName}
+			}
+			filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(multipleFilesArtifactsMountDirPaths, builtin.serviceNetwork)
 			if interpretationErr != nil {
 				return nil, interpretationErr
 			}
@@ -233,7 +237,7 @@ func (builtin *RunPythonCapabilities) Interpret(_ string, arguments *builtin_arg
 
 func (builtin *RunPythonCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError {
 	// TODO add validation for python script
-	var serviceDirpathsToArtifactIdentifiers map[string]string
+	var serviceDirpathsToArtifactIdentifiers map[string][]string
 	if builtin.serviceConfig.GetFilesArtifactsExpansion() != nil {
 		serviceDirpathsToArtifactIdentifiers = builtin.serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers
 	}
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go
index fbdb8363c..8c7b06065 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go
@@ -136,7 +136,11 @@ func (builtin *RunShCapabilities) Interpret(_ string, arguments *builtin_argumen
 			if interpretationErr != nil {
 				return nil, interpretationErr
 			}
-			filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(filesArtifactMountDirPaths, builtin.serviceNetwork)
+			multipleFilesArtifactsMountDirPaths := map[string][]string{}
+			for pathToFile, fileArtifactName := range filesArtifactMountDirPaths {
+				multipleFilesArtifactsMountDirPaths[pathToFile] = []string{fileArtifactName}
+			}
+			filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(multipleFilesArtifactsMountDirPaths, builtin.serviceNetwork)
 			if interpretationErr != nil {
 				return nil, interpretationErr
 			}
@@ -179,7 +183,7 @@ func (builtin *RunShCapabilities) Interpret(_ string, arguments *builtin_argumen
 
 func (builtin *RunShCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError {
 	// TODO validate bash
-	var serviceDirpathsToArtifactIdentifiers map[string]string
+	var serviceDirpathsToArtifactIdentifiers map[string][]string
 	if builtin.serviceConfig.GetFilesArtifactsExpansion() != nil {
 		serviceDirpathsToArtifactIdentifiers = builtin.serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers
 	}
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go
index b7ae7624e..f10d1c7cd 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go
@@ -134,7 +134,7 @@ func createInterpretationResult(resultUuid string, storeSpecList []*store_spec.S
 	return result
 }
 
-func validateTasksCommon(validatorEnvironment *startosis_validator.ValidatorEnvironment, storeSpecList []*store_spec.StoreSpec, serviceDirpathsToArtifactIdentifiers map[string]string, imageName string) *startosis_errors.ValidationError {
+func validateTasksCommon(validatorEnvironment *startosis_validator.ValidatorEnvironment, storeSpecList []*store_spec.StoreSpec, serviceDirpathsToArtifactIdentifiers map[string][]string, imageName string) *startosis_errors.ValidationError {
 	if storeSpecList != nil {
 		if err := validatePathIsUniqueWhileCreatingFileArtifact(storeSpecList); err != nil {
 			return startosis_errors.WrapWithValidationError(err, "error occurred while validating file paths to copy into file artifact")
@@ -145,9 +145,11 @@ func validateTasksCommon(validatorEnvironment *startosis_validator.ValidatorEnvi
 		}
 	}
 
-	for _, artifactName := range serviceDirpathsToArtifactIdentifiers {
-		if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
-			return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", RunPythonBuiltinName, artifactName)
+	for _, artifactNames := range serviceDirpathsToArtifactIdentifiers {
+		for _, artifactName := range artifactNames {
+			if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
+				return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", RunPythonBuiltinName, artifactName)
+			}
 		}
 	}
 
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go
index 605afed4e..618cc6009 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go
@@ -1,6 +1,7 @@
 package builtin_argument
 
 import (
+	"fmt"
 	"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service"
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors"
 	"go.starlark.net/starlark"
@@ -21,6 +22,25 @@ func NonEmptyString(value starlark.Value, argNameForLogging string) *startosis_e
 	return nil
 }
 
+func StringListWithNotEmptyValues(value starlark.Value, argNameForLogging string) *startosis_errors.InterpretationError {
+	starlarkList, ok := value.(*starlark.List)
+	if !ok {
+		return startosis_errors.NewInterpretationError("Value for '%s' was expected to be a starlark.List but was '%s'", argNameForLogging, reflect.TypeOf(value))
+	}
+	iterator := starlarkList.Iterate()
+	defer iterator.Done()
+	var itemValue starlark.Value
+	var index = 0
+	for iterator.Next(&itemValue) {
+		argumentDescription := fmt.Sprintf("element %d in argument '%s'", index, argNameForLogging)
+		if interpretationErr := NonEmptyString(itemValue, argumentDescription); interpretationErr != nil {
+			return interpretationErr
+		}
+		index++
+	}
+	return nil
+}
+
 func Uint64InRange(value starlark.Value, argNameForLogging string, min uint64, max uint64) *startosis_errors.InterpretationError {
 	valueInt, ok := value.(starlark.Int)
 	if !ok {
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/arg_shared_helpers.go b/core/server/api_container/server/startosis_engine/kurtosis_types/arg_shared_helpers.go
index c81e1c7f3..ec399c870 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_types/arg_shared_helpers.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_types/arg_shared_helpers.go
@@ -59,6 +59,31 @@ func SafeCastToMapStringString(expectedValue starlark.Value, argNameForLogging s
 	return castValue, nil
 }
 
+// TODO: make private once arg_parser don't need it anymore
+func SafeCastToMapStringStringSlice(expectedValue starlark.Value, argNameForLogging string) (map[string][]string, *startosis_errors.InterpretationError) {
+	dictValue, ok := expectedValue.(*starlark.Dict)
+	if !ok {
+		return nil, startosis_errors.NewInterpretationError("'%s' argument is expected to be a dict. Got %s", argNameForLogging, reflect.TypeOf(expectedValue))
+	}
+	castValue := make(map[string][]string)
+	for _, key := range dictValue.Keys() {
+		stringKey, castErr := SafeCastToString(key, fmt.Sprintf("%v.key:%v", argNameForLogging, key))
+		if castErr != nil {
+			return nil, castErr
+		}
+		value, found, dictErr := dictValue.Get(key)
+		if !found || dictErr != nil {
+			return nil, startosis_errors.NewInterpretationError("'%s' key in dict '%s' doesn't have a value we could retrieve. This is a Kurtosis bug.", key.String(), argNameForLogging)
+		}
+		stringSliceValue, castErr := SafeCastToStringSlice(value, fmt.Sprintf("%v[\"%v\"]", argNameForLogging, stringKey))
+		if castErr != nil {
+			return nil, castErr
+		}
+		castValue[stringKey] = stringSliceValue
+	}
+	return castValue, nil
+}
+
 // TODO: make private once arg_parser don't need it anymore
 func SafeCastToString(expectedValueString starlark.Value, argNameForLogging string) (string, *startosis_errors.InterpretationError) {
 	castValue, ok := expectedValueString.(starlark.String)
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/directory/directory.go b/core/server/api_container/server/startosis_engine/kurtosis_types/directory/directory.go
index 354b39ae0..267c788a6 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_types/directory/directory.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_types/directory/directory.go
@@ -4,6 +4,7 @@ import (
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework"
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument"
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_type_constructor"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types"
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_constants"
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors"
 	"go.starlark.net/starlark"
@@ -13,7 +14,7 @@ import (
 const (
 	DirectoryTypeName = "Directory"
 
-	ArtifactNameAttr  = "artifact_name"
+	ArtifactNamesAttr = "artifact_names"
 	PersistentKeyAttr = "persistent_key"
 	SizeKeyAttr       = "size"
 
@@ -27,11 +28,11 @@ func NewDirectoryType() *kurtosis_type_constructor.KurtosisTypeConstructor {
 
 			Arguments: []*builtin_argument.BuiltinArgument{
 				{
-					Name:              ArtifactNameAttr,
+					Name:              ArtifactNamesAttr,
 					IsOptional:        true,
-					ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String],
+					ZeroValueProvider: builtin_argument.ZeroValueProvider[*starlark.List],
 					Validator: func(value starlark.Value) *startosis_errors.InterpretationError {
-						return builtin_argument.NonEmptyString(value, ArtifactNameAttr)
+						return builtin_argument.StringListWithNotEmptyValues(value, ArtifactNamesAttr)
 					},
 				},
 				{
@@ -39,7 +40,7 @@ func NewDirectoryType() *kurtosis_type_constructor.KurtosisTypeConstructor {
 					IsOptional:        true,
 					ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String],
 					Validator: func(value starlark.Value) *startosis_errors.InterpretationError {
-						return builtin_argument.NonEmptyString(value, ArtifactNameAttr)
+						return builtin_argument.NonEmptyString(value, ArtifactNamesAttr)
 					},
 				},
 				{
@@ -84,7 +85,7 @@ func CreateDirectoryFromFilesArtifact(
 
 	argumentDefinitions := NewDirectoryType().KurtosisBaseBuiltin.Arguments
 	argumentValuesSet := builtin_argument.NewArgumentValuesSet(argumentDefinitions, args)
-	kurtosisDefaultValue, interpretationErr := kurtosis_type_constructor.CreateKurtosisStarlarkTypeDefault(ArtifactNameAttr, argumentValuesSet)
+	kurtosisDefaultValue, interpretationErr := kurtosis_type_constructor.CreateKurtosisStarlarkTypeDefault(ArtifactNamesAttr, argumentValuesSet)
 	if interpretationErr != nil {
 		return nil, interpretationErr
 	}
@@ -103,16 +104,34 @@ func (directory *Directory) Copy() (builtin_argument.KurtosisValueType, error) {
 	}, nil
 }
 
-func (directory *Directory) GetArtifactNameIfSet() (string, bool, *startosis_errors.InterpretationError) {
-	fileArtifact, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](
-		directory.KurtosisValueTypeDefault, ArtifactNameAttr)
-	if interpretationErr != nil {
-		return "", false, interpretationErr
-	}
+func (directory *Directory) GetArtifactNamesIfSet() ([]string, bool, *startosis_errors.InterpretationError) {
+	artifactNames := []string{}
+	fileArtifacts, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[*starlark.List](
+		directory.KurtosisValueTypeDefault, ArtifactNamesAttr)
 	if !found {
-		return "", false, nil
+		return nil, false, nil
+	}
+	if interpretationErr != nil {
+		var fileArtifact starlark.String
+		fileArtifact, found, interpretationErr = kurtosis_type_constructor.ExtractAttrValue[starlark.String](
+			directory.KurtosisValueTypeDefault, ArtifactNamesAttr)
+		if interpretationErr != nil {
+			return nil, false, interpretationErr
+		}
+		if !found {
+			return nil, false, nil
+		}
+		artifactNames = append(artifactNames, fileArtifact.GoString())
+	} else {
+		if fileArtifacts.Len() > 0 {
+			artifactNames, interpretationErr = kurtosis_types.SafeCastToStringSlice(fileArtifacts, ArtifactNamesAttr)
+			if interpretationErr != nil {
+				return nil, false, interpretationErr
+			}
+		}
 	}
-	return fileArtifact.GoString(), true, nil
+
+	return artifactNames, true, nil
 }
 
 func (directory *Directory) GetPersistentKeyIfSet() (string, bool, *startosis_errors.InterpretationError) {
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go
index efb20ac47..298c12f65 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go
@@ -6,6 +6,7 @@ import (
 	"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec"
 	"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service"
 	"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory"
+	"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/uuid_generator"
 	"github.com/kurtosis-tech/kurtosis/core/files_artifacts_expander/args"
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network"
 	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework"
@@ -276,7 +277,7 @@ func (config *ServiceConfig) ToKurtosisType(
 		return nil, interpretationErr
 	}
 	if found {
-		var filesArtifactsMountDirpathsMap map[string]string
+		var filesArtifactsMountDirpathsMap map[string][]string
 		filesArtifactsMountDirpathsMap, persistentDirectoriesDirpathsMap, interpretationErr := convertFilesArguments(FilesAttr, filesStarlark)
 		if interpretationErr != nil {
 			return nil, interpretationErr
@@ -458,19 +459,25 @@ func (config *ServiceConfig) GetReadyCondition() (*ReadyCondition, *startosis_er
 	return readyConditions, nil
 }
 
-func ConvertFilesArtifactsMounts(filesArtifactsMountDirpathsMap map[string]string, serviceNetwork service_network.ServiceNetwork) (*service_directory.FilesArtifactsExpansion, *startosis_errors.InterpretationError) {
+func ConvertFilesArtifactsMounts(filesArtifactsMountDirpathsMap map[string][]string, serviceNetwork service_network.ServiceNetwork) (*service_directory.FilesArtifactsExpansion, *startosis_errors.InterpretationError) {
 	filesArtifactsExpansions := []args.FilesArtifactExpansion{}
-	serviceDirpathsToArtifactIdentifiers := map[string]string{}
+	serviceDirpathsToArtifactIdentifiers := map[string][]string{}
 	expanderDirpathToUserServiceDirpathMap := map[string]string{}
-	for mountpointOnUserService, filesArtifactIdentifier := range filesArtifactsMountDirpathsMap {
-		dirpathToExpandTo := path.Join(filesArtifactExpansionDirsParentDirpath, filesArtifactIdentifier)
-		expansion := args.FilesArtifactExpansion{
-			FilesIdentifier:   filesArtifactIdentifier,
-			DirPathToExpandTo: dirpathToExpandTo,
+	for mountpointOnUserService, filesArtifactIdentifiers := range filesArtifactsMountDirpathsMap {
+		filesArtifactFolderUUIDStr, err := uuid_generator.GenerateUUIDString()
+		if err != nil {
+			return nil, startosis_errors.NewInterpretationError("An error occurred generating an UUID string for the file artifacts destination folder")
+		}
+		dirpathToExpandTo := path.Join(filesArtifactExpansionDirsParentDirpath, filesArtifactFolderUUIDStr)
+		for _, filesArtifactIdentifier := range filesArtifactIdentifiers {
+			expansion := args.FilesArtifactExpansion{
+				FilesIdentifier:   filesArtifactIdentifier,
+				DirPathToExpandTo: dirpathToExpandTo,
+			}
+			filesArtifactsExpansions = append(filesArtifactsExpansions, expansion)
 		}
-		filesArtifactsExpansions = append(filesArtifactsExpansions, expansion)
-		serviceDirpathsToArtifactIdentifiers[mountpointOnUserService] = filesArtifactIdentifier
 		expanderDirpathToUserServiceDirpathMap[dirpathToExpandTo] = mountpointOnUserService
+		serviceDirpathsToArtifactIdentifiers[mountpointOnUserService] = filesArtifactIdentifiers
 	}
 
 	// TODO: Sad that we need the service network here to get the APIC info. This is wrong, we should fix this by
@@ -525,8 +532,8 @@ func convertPortMapEntry(attrNameForLogging string, key starlark.Value, value st
 	return keyStr.GoString(), servicePortSpec, nil
 }
 
-func convertFilesArguments(attrNameForLogging string, filesDict *starlark.Dict) (map[string]string, map[string]service_directory.PersistentDirectory, *startosis_errors.InterpretationError) {
-	filesArtifacts := map[string]string{}
+func convertFilesArguments(attrNameForLogging string, filesDict *starlark.Dict) (map[string][]string, map[string]service_directory.PersistentDirectory, *startosis_errors.InterpretationError) {
+	filesArtifacts := map[string][]string{}
 	persistentDirectories := map[string]service_directory.PersistentDirectory{}
 	for _, fileItem := range filesDict.Items() {
 		rawDirPath := fileItem[0]
@@ -536,20 +543,21 @@ func convertFilesArguments(attrNameForLogging string, filesDict *starlark.Dict)
 		}
 
 		var interpretationErr *startosis_errors.InterpretationError
-		rawDirectoryObj := fileItem[1]
-		directoryObj, isDirectoryArg := rawDirectoryObj.(*directory.Directory)
+		rawFileValue := fileItem[1]
+		directoryObj, isDirectoryArg := rawFileValue.(*directory.Directory)
 		if !isDirectoryArg {
+
 			// we're also supporting raw strings as well and transform them into files artifact name.
-			directoryObjAsStr, isSimpleStringArg := rawDirectoryObj.(starlark.String)
+			fileArtifactNameStr, isSimpleStringArg := rawFileValue.(starlark.String)
 			if !isSimpleStringArg {
 				return nil, nil, startosis_errors.NewInterpretationError("Unable to convert value of '%s' dictionary '%v' to a Directory object", attrNameForLogging, filesDict)
 			}
-			directoryObj, interpretationErr = directory.CreateDirectoryFromFilesArtifact(directoryObjAsStr.GoString())
+			directoryObj, interpretationErr = directory.CreateDirectoryFromFilesArtifact(fileArtifactNameStr.GoString())
 			if interpretationErr != nil {
 				return nil, nil, interpretationErr
 			}
 		}
-		artifactName, artifactNameSet, interpretationErr := directoryObj.GetArtifactNameIfSet()
+		artifactNames, artifactNameSet, interpretationErr := directoryObj.GetArtifactNamesIfSet()
 		if interpretationErr != nil {
 			return nil, nil, interpretationErr
 		}
@@ -564,10 +572,10 @@ func convertFilesArguments(attrNameForLogging string, filesDict *starlark.Dict)
 		if artifactNameSet == persistentKeySet {
 			// this condition is a XOR
 			return nil, nil, startosis_errors.NewInterpretationError("Parameter '%s' and '%s' cannot be set on the same '%s' object: '%s'",
-				directory.ArtifactNameAttr, directory.PersistentKeyAttr, directory.DirectoryTypeName, directoryObj.String())
+				directory.ArtifactNamesAttr, directory.PersistentKeyAttr, directory.DirectoryTypeName, directoryObj.String())
 		}
 		if artifactNameSet {
-			filesArtifacts[dirPath.GoString()] = artifactName
+			filesArtifacts[dirPath.GoString()] = artifactNames
 		} else {
 			// persistentKey is necessarily set since we checked the exclusivity above
 			persistentDirectories[dirPath.GoString()] = service_directory.PersistentDirectory{
diff --git a/docs/docs/api-reference/starlark-reference/directory.md b/docs/docs/api-reference/starlark-reference/directory.md
index d6a717e9f..e14a151b3 100644
--- a/docs/docs/api-reference/starlark-reference/directory.md
+++ b/docs/docs/api-reference/starlark-reference/directory.md
@@ -5,12 +5,12 @@ sidebar_label: Directory
 
 The `Directory` constructor creates a `Directory` object that represents a directory inside an existing service (see
 the [ServiceConfig][service-config] object).
-Directory object can be either a files artifact or a persistent directory, depending on the arguments passed to the 
+Directory object can be a files artifacts list or a persistent directory, depending on the arguments passed to the 
 constructor.
 
 ```python
 file_artifact_directory = Directory(
-    artifact_name=files_artifact_1,
+    artifact_names=[files_artifact_1, files_artifact_2] 
 )
 # Or:
 persistent_directory = Directory(
@@ -18,11 +18,14 @@ persistent_directory = Directory(
 )
 ```
 
-A directory composed of a files artifact will be automatically provisioned with the given files artifact content. In 
-the above example, `files_artifact_1` is a files artifact name. (see [upload_files][upload-files-reference], 
+A directory composed of one or many files artifacts will be automatically provisioned with the given files artifacts content. In 
+the above examples, `files_artifact_1` and `files_artifact_2` are files artifact names. (see [upload_files][upload-files-reference], 
 [render_templates][render-templates-reference] and [store_service_files][store-service-reference] to learn more about 
 on how to create file artifacts). 
 
+:::warning
+Take into account when using multiple file artifacts, like the example, if both files artifact contains a file or folder with the same name this will end up overwritten.
+
 A persistent directory, as its name indicates, persists over service updates and restarts. It is uniquely identified 
 by its `persistent_key` (a persistent directory cannot be shared across
 multiple services). When it is first created, it will be empty. The service can write anything in it. When the service 
diff --git a/docs/docs/api-reference/starlark-reference/service-config.md b/docs/docs/api-reference/starlark-reference/service-config.md
index 33249f6f9..0805b1451 100644
--- a/docs/docs/api-reference/starlark-reference/service-config.md
+++ b/docs/docs/api-reference/starlark-reference/service-config.md
@@ -58,7 +58,7 @@ config = ServiceConfig(
     files = {
         "path/to/files/artifact_1/": files_artifact_1,
         "path/to/files/artifact_2/": Directory(
-            artifact_name=files_artifact_2,
+            artifact_names=[files_artifact_2],
         ),
         "path/to/persistent/directory/": Directory(
             persistent_key="data-directory",


Test patch:
diff --git a/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go b/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go
index 4a4146a49..d5a6ad0fb 100644
--- a/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go
+++ b/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go
@@ -97,9 +97,9 @@ func testFilesArtifactExpansion() *service_directory.FilesArtifactsExpansion {
 			"ENV_VAR1": "env_var1_value",
 			"ENV_VAR2": "env_var2_value",
 		},
-		ServiceDirpathsToArtifactIdentifiers: map[string]string{
-			"/pahth/number1": "first_identifier",
-			"/path/number2":  "second_idenfifier",
+		ServiceDirpathsToArtifactIdentifiers: map[string][]string{
+			"/path/number1": {"first_identifier"},
+			"/path/number2": {"second_identifier"},
 		},
 		ExpanderDirpathsToServiceDirpaths: map[string]string{
 			"/expander/dir1": "/service/dir1",
diff --git a/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go b/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go
index 4705094af..b7201e363 100644
--- a/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go
+++ b/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go
@@ -342,9 +342,9 @@ func testFilesArtifactExpansion() *service_directory.FilesArtifactsExpansion {
 			"ENV_VAR1": "env_var1_value",
 			"ENV_VAR2": "env_var2_value",
 		},
-		ServiceDirpathsToArtifactIdentifiers: map[string]string{
-			"/pahth/number1": "first_identifier",
-			"/path/number2":  "second_idenfifier",
+		ServiceDirpathsToArtifactIdentifiers: map[string][]string{
+			"/path/number1": {"first_identifier"},
+			"/path/number2": {"second_identifier"},
 		},
 		ExpanderDirpathsToServiceDirpaths: map[string]string{
 			"/expander/dir1": "/service/dir1",
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_file_artifact_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_file_artifact_framework_test.go
index 927235cc5..483ea78f7 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_file_artifact_framework_test.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_file_artifact_framework_test.go
@@ -20,17 +20,17 @@ func (suite *KurtosisTypeConstructorTestSuite) TestDirectoryFileArtifact() {
 }
 
 func (t *directoryFileArtifactTestCase) GetStarlarkCode() string {
-	return fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.ArtifactNameAttr, testFilesArtifactName1)
+	return fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1)
 }
 
 func (t *directoryFileArtifactTestCase) Assert(typeValue builtin_argument.KurtosisValueType) {
 	directoryStarlark, ok := typeValue.(*directory.Directory)
 	require.True(t, ok)
 
-	artifactName, found, err := directoryStarlark.GetArtifactNameIfSet()
+	artifactNames, found, err := directoryStarlark.GetArtifactNamesIfSet()
 	require.Nil(t, err)
 	require.True(t, found)
-	require.Equal(t, testFilesArtifactName1, artifactName)
+	require.Equal(t, []string{testFilesArtifactName1}, artifactNames)
 
 	persistentKey, found, err := directoryStarlark.GetPersistentKeyIfSet()
 	require.Nil(t, err)
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_multiple_file_artifacts_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_multiple_file_artifacts_framework_test.go
new file mode 100644
index 000000000..f78c880cb
--- /dev/null
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_multiple_file_artifacts_framework_test.go
@@ -0,0 +1,33 @@
+package test_engine
+
+import (
+	"fmt"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/directory"
+	"github.com/stretchr/testify/require"
+	"testing"
+)
+
+type directoryMultipleFileArtifactsTestCase struct {
+	*testing.T
+}
+
+func (suite *KurtosisTypeConstructorTestSuite) TestDirectoryMultipleFileArtifacts() {
+	suite.run(&directoryMultipleFileArtifactsTestCase{
+		T: suite.T(),
+	})
+}
+
+func (t *directoryMultipleFileArtifactsTestCase) GetStarlarkCode() string {
+	return fmt.Sprintf("%s(%s=[%q, %q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1, testFilesArtifactName2)
+}
+
+func (t *directoryMultipleFileArtifactsTestCase) Assert(typeValue builtin_argument.KurtosisValueType) {
+	directoryStarlark, ok := typeValue.(*directory.Directory)
+	require.True(t, ok)
+
+	artifactNames, found, err := directoryStarlark.GetArtifactNamesIfSet()
+	require.Nil(t, err)
+	require.True(t, found)
+	require.Equal(t, []string{testFilesArtifactName1, testFilesArtifactName2}, artifactNames)
+}
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_persistent_directory_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_persistent_directory_framework_test.go
index 64e81c595..ca6aff71f 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_persistent_directory_framework_test.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/directory_persistent_directory_framework_test.go
@@ -26,10 +26,10 @@ func (t *directoryPersistentDirectoryTestCase) Assert(typeValue builtin_argument
 	directoryStarlark, ok := typeValue.(*directory.Directory)
 	require.True(t, ok)
 
-	artifactName, found, err := directoryStarlark.GetArtifactNameIfSet()
+	artifactNames, found, err := directoryStarlark.GetArtifactNamesIfSet()
 	require.Nil(t, err)
 	require.False(t, found)
-	require.Empty(t, artifactName)
+	require.Empty(t, artifactNames)
 
 	persistentKey, found, err := directoryStarlark.GetPersistentKeyIfSet()
 	require.Nil(t, err)
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_backward_compatible_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_backward_compatible_test.go
index e63e6ec2e..d68923479 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_backward_compatible_test.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_backward_compatible_test.go
@@ -82,9 +82,9 @@ func (t *serviceConfigFullTestCaseBackwardCompatible) Assert(typeValue builtin_a
 	}
 	require.Equal(t, expectedPublicPorts, serviceConfig.GetPublicPorts())
 
-	expectedFilesArtifactMap := map[string]string{
-		testFilesArtifactPath1: testFilesArtifactName1,
-		testFilesArtifactPath2: testFilesArtifactName2,
+	expectedFilesArtifactMap := map[string][]string{
+		testFilesArtifactPath1: {testFilesArtifactName1},
+		testFilesArtifactPath2: {testFilesArtifactName2},
 	}
 	require.NotNil(t, serviceConfig.GetFilesArtifactsExpansion())
 	require.Equal(t, expectedFilesArtifactMap, serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers)
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go
index 3657dc6b2..875448e92 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go
@@ -35,8 +35,8 @@ func (suite *KurtosisTypeConstructorTestSuite) TestServiceConfigFull() {
 }
 
 func (t *serviceConfigFullTestCase) GetStarlarkCode() string {
-	fileArtifact1 := fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.ArtifactNameAttr, testFilesArtifactName1)
-	fileArtifact2 := fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.ArtifactNameAttr, testFilesArtifactName2)
+	fileArtifact1 := fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1)
+	fileArtifact2 := fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName2)
 	persistentDirectory := fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.PersistentKeyAttr, testPersistentDirectoryKey)
 	starlarkCode := fmt.Sprintf("%s(%s=%q, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%q, %s=%d, %s=%d, %s=%d, %s=%d, %s=%s, %s=%v)",
 		service_config.ServiceConfigTypeName,
@@ -90,9 +90,9 @@ func (t *serviceConfigFullTestCase) Assert(typeValue builtin_argument.KurtosisVa
 	}
 	require.Equal(t, expectedPublicPorts, serviceConfig.GetPublicPorts())
 
-	expectedFilesArtifactMap := map[string]string{
-		testFilesArtifactPath1: testFilesArtifactName1,
-		testFilesArtifactPath2: testFilesArtifactName2,
+	expectedFilesArtifactMap := map[string][]string{
+		testFilesArtifactPath1: {testFilesArtifactName1},
+		testFilesArtifactPath2: {testFilesArtifactName2},
 	}
 	require.NotNil(t, serviceConfig.GetFilesArtifactsExpansion())
 	require.Equal(t, expectedFilesArtifactMap, serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers)
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_multiple_files_in_same_folder_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_multiple_files_in_same_folder_test.go
new file mode 100644
index 000000000..02bac8a2b
--- /dev/null
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_multiple_files_in_same_folder_test.go
@@ -0,0 +1,64 @@
+package test_engine
+
+import (
+	"fmt"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/directory"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config"
+	"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages"
+	"github.com/stretchr/testify/require"
+	"net"
+	"testing"
+)
+
+type serviceConfigMultipleFilesInSameFolderTestCase struct {
+	*testing.T
+	serviceNetwork         *service_network.MockServiceNetwork
+	packageContentProvider *startosis_packages.MockPackageContentProvider
+}
+
+func (suite *KurtosisTypeConstructorTestSuite) TestServiceConfigMultipleFilesInSameFolder() {
+
+	suite.serviceNetwork.EXPECT().GetApiContainerInfo().Times(1).Return(
+		service_network.NewApiContainerInfo(net.IPv4(0, 0, 0, 0), 0, "0.0.0"),
+	)
+
+	suite.run(&serviceConfigMultipleFilesInSameFolderTestCase{
+		T:                      suite.T(),
+		serviceNetwork:         suite.serviceNetwork,
+		packageContentProvider: suite.packageContentProvider,
+	})
+}
+
+func (t *serviceConfigMultipleFilesInSameFolderTestCase) GetStarlarkCode() string {
+	filesArtifactsDirectory := fmt.Sprintf("%s(%s=[%q, %q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1, testFilesArtifactName2)
+	starlarkCode := fmt.Sprintf("%s(%s=%q, %s=%s)",
+		service_config.ServiceConfigTypeName,
+		service_config.ImageAttr, testContainerImageName,
+		service_config.FilesAttr, fmt.Sprintf("{%q: %s}", testFilesArtifactPath1, filesArtifactsDirectory),
+	)
+
+	return starlarkCode
+}
+
+func (t *serviceConfigMultipleFilesInSameFolderTestCase) Assert(typeValue builtin_argument.KurtosisValueType) {
+	serviceConfigStarlark, ok := typeValue.(*service_config.ServiceConfig)
+	require.True(t, ok)
+
+	serviceConfig, err := serviceConfigStarlark.ToKurtosisType(
+		t.serviceNetwork,
+		testModulePackageId,
+		testModuleMainFileLocator,
+		t.packageContentProvider,
+		testNoPackageReplaceOptions)
+	require.Nil(t, err)
+
+	require.Equal(t, testContainerImageName, serviceConfig.GetContainerImageName())
+
+	expectedFilesArtifactMap := map[string][]string{
+		testFilesArtifactPath1: {testFilesArtifactName1, testFilesArtifactName2},
+	}
+	require.NotNil(t, serviceConfig.GetFilesArtifactsExpansion())
+	require.Equal(t, expectedFilesArtifactMap, serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers)
+}
diff --git a/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_directory_testsuite_test.go b/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_directory_testsuite_test.go
new file mode 100644
index 000000000..f91651e04
--- /dev/null
+++ b/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_directory_testsuite_test.go
@@ -0,0 +1,71 @@
+package startosis_directory_test
+
+import (
+	"context"
+	"github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config"
+	"os"
+	"path"
+	"testing"
+
+	"github.com/kurtosis-tech/kurtosis-cli/golang_internal_testsuite/test_helpers"
+	"github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves"
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/require"
+	"github.com/stretchr/testify/suite"
+)
+
+const (
+	name           = "startosis-directory"
+	emptyRunParams = "{}"
+)
+
+type StartosisDirectoryTestSuite struct {
+	suite.Suite
+	enclaveCtx         *enclaves.EnclaveContext
+	destroyEnclaveFunc func() error
+}
+
+func TestStartosisDirectoryTestSuite(t *testing.T) {
+	suite.Run(t, new(StartosisDirectoryTestSuite))
+}
+
+func (suite *StartosisDirectoryTestSuite) SetupTest() {
+	ctx := context.Background()
+	t := suite.T()
+	enclaveCtx, _, destroyEnclaveFunc, err := test_helpers.CreateEnclave(t, ctx, name)
+	require.NoError(t, err, "An error occurred creating an enclave")
+	suite.enclaveCtx = enclaveCtx
+	suite.destroyEnclaveFunc = destroyEnclaveFunc
+}
+
+func (suite *StartosisDirectoryTestSuite) TearDownTest() {
+	err := suite.destroyEnclaveFunc()
+	require.NoError(suite.T(), err, "Destroying the test suite's enclave process has failed, you will have to remove it manually")
+}
+
+func (suite *StartosisDirectoryTestSuite) RunScript(ctx context.Context, script string) (*enclaves.StarlarkRunResult, error) {
+	logrus.Infof("Executing Startosis script...")
+	logrus.Debugf("Startosis script content: \n%v", script)
+
+	return test_helpers.RunScriptWithDefaultConfig(ctx, suite.enclaveCtx, script)
+}
+
+func (suite *StartosisDirectoryTestSuite) RunPackage(ctx context.Context, packageRelativeDirpath string) (*enclaves.StarlarkRunResult, error) {
+	return suite.RunPackageWithParams(ctx, packageRelativeDirpath, emptyRunParams)
+}
+
+func (suite *StartosisDirectoryTestSuite) RunPackageWithParams(ctx context.Context, packageRelativeDirpath string, params string) (*enclaves.StarlarkRunResult, error) {
+	logrus.Infof("Executing Startosis package...")
+
+	currentWorkingDirectory, err := os.Getwd()
+	require.Nil(suite.T(), err)
+	packageDirpath := path.Join(currentWorkingDirectory, packageRelativeDirpath)
+
+	logrus.Debugf("Startosis package dirpath: %v", packageDirpath)
+
+	return suite.enclaveCtx.RunStarlarkPackageBlocking(
+		ctx,
+		packageDirpath,
+		starlark_run_config.NewRunStarlarkConfig(starlark_run_config.WithSerializedParams(params)),
+	)
+}
diff --git a/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_mutiple_artifacts_directory_test.go b/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_mutiple_artifacts_directory_test.go
new file mode 100644
index 000000000..4ea7eb769
--- /dev/null
+++ b/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_mutiple_artifacts_directory_test.go
@@ -0,0 +1,27 @@
+package startosis_directory_test
+
+import (
+	"context"
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/require"
+)
+
+const (
+	multipleArtifactsDirectoryPackage = "../../../starlark/service-directory/multiple-artifacts-directory"
+)
+
+func (suite *StartosisDirectoryTestSuite) TestAddServiceWithMultipleArtifactsDirectory() {
+	ctx := context.Background()
+	runResult, err := suite.RunPackage(ctx, multipleArtifactsDirectoryPackage)
+
+	t := suite.T()
+	logrus.Infof("Test Output: %v", runResult.RunOutput)
+	require.NoError(t, err, "Unexpected error executing starlark package")
+	require.Nil(t, runResult.InterpretationError, "Unexpected interpretation error.")
+	require.Empty(t, runResult.ValidationErrors, "Unexpected validation error")
+	require.Nil(t, runResult.ExecutionError, "Unexpected execution error")
+
+	// This checks the output of the `cat both files` at the end.
+	require.Contains(t, runResult.RunOutput, "hello")
+	require.Contains(t, runResult.RunOutput, "world")
+}
diff --git a/internal_testsuites/golang/testsuite/startosis_persistent_directory_test/startosis_persistent_directory_test.go b/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_persistent_directory_test.go
similarity index 65%
rename from internal_testsuites/golang/testsuite/startosis_persistent_directory_test/startosis_persistent_directory_test.go
rename to internal_testsuites/golang/testsuite/startosis_directory_test/startosis_persistent_directory_test.go
index 5376c8f09..502e2a4fd 100644
--- a/internal_testsuites/golang/testsuite/startosis_persistent_directory_test/startosis_persistent_directory_test.go
+++ b/internal_testsuites/golang/testsuite/startosis_directory_test/startosis_persistent_directory_test.go
@@ -1,17 +1,13 @@
-package startosis_persistent_directory_test
+package startosis_directory_test
 
 import (
 	"context"
 	"fmt"
-	"github.com/kurtosis-tech/kurtosis-cli/golang_internal_testsuite/test_helpers"
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
-	"testing"
 )
 
 const (
-	testName = "persist-data-test"
-
 	addServiceWriteLineToFile = `
 IMAGE = "docker/getting-started"
 SERVICE_NAME = "test-service"
@@ -48,33 +44,25 @@ def run(plan):
 `
 )
 
-func TestAddServiceAndPersistentFileToDirectory(t *testing.T) {
-
+func (suite *StartosisDirectoryTestSuite) TestAddServiceAndPersistentFileToDirectory() {
 	ctx := context.Background()
-	// ------------------------------------- ENGINE SETUP ----------------------------------------------
-	enclaveCtx, _, destroyEnclaveFunc, err := test_helpers.CreateEnclave(t, ctx, testName)
-	require.NoError(t, err, "An error occurred creating an enclave")
-	defer func() {
-		err = destroyEnclaveFunc()
-		require.NoError(t, err, "An error occurred destroying the enclave after the test finished")
-	}()
-
-	// ------------------------------------- TEST RUN ----------------------------------------------
 	firstScript := fmt.Sprintf(addServiceWriteLineToFile, 100)
-	firstRunResult, err := test_helpers.RunScriptWithDefaultConfig(ctx, enclaveCtx, firstScript)
+	firstRunResult, err := suite.RunScript(ctx, firstScript)
+
+	t := suite.T()
 	logrus.Infof("Test Output: %v", firstRunResult)
 	require.NoError(t, err, "Unexpected error executing starlark script")
-	require.Nil(t, firstRunResult.InterpretationError, "Unexpected interpretation error. This test requires you to be online for the read_file command to run")
+	require.Nil(t, firstRunResult.InterpretationError, "Unexpected interpretation error.")
 	require.Empty(t, firstRunResult.ValidationErrors, "Unexpected validation error")
 	require.Nil(t, firstRunResult.ExecutionError, "Unexpected execution error")
 	// This checks the output of the `wc -l` at the end. For the first run, the file should contain one line only
 	require.Contains(t, firstRunResult.RunOutput, "\n1 /data/test.log\n")
 
 	secondScript := fmt.Sprintf(addServiceWriteLineToFile, 150) // we slightly change the service config so that the service gets restarted and the execs re-run
-	secondRunResult, err := test_helpers.RunScriptWithDefaultConfig(ctx, enclaveCtx, secondScript)
+	secondRunResult, err := suite.RunScript(ctx, secondScript)
 	logrus.Infof("Test Output: %v", secondRunResult)
 	require.NoError(t, err, "Unexpected error executing starlark script")
-	require.Nil(t, secondRunResult.InterpretationError, "Unexpected interpretation error. This test requires you to be online for the read_file command to run")
+	require.Nil(t, secondRunResult.InterpretationError, "Unexpected interpretation error.")
 	require.Empty(t, secondRunResult.ValidationErrors, "Unexpected validation error")
 	require.Nil(t, secondRunResult.ExecutionError, "Unexpected execution error")
 	// For the second run, it should contain 2 lines as the file persisted between the 2 runs
diff --git a/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/folder-1/file-1.txt b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/folder-1/file-1.txt
new file mode 100644
index 000000000..b6fc4c620
--- /dev/null
+++ b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/folder-1/file-1.txt
@@ -0,0 +1,1 @@
+hello
\ No newline at end of file
diff --git a/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/folder-2/file-2.txt b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/folder-2/file-2.txt
new file mode 100644
index 000000000..04fea0642
--- /dev/null
+++ b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/folder-2/file-2.txt
@@ -0,0 +1,1 @@
+world
\ No newline at end of file
diff --git a/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/kurtosis.yml b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/kurtosis.yml
new file mode 100644
index 000000000..154f4bf54
--- /dev/null
+++ b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/kurtosis.yml
@@ -0,0 +1,1 @@
+name: "github.com/kurtosis-tech/kurtosis/internal_testsuites/starlark/service-directory/multiple-artifacts-directory"
diff --git a/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/main.star b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/main.star
new file mode 100644
index 000000000..0a9c4ea47
--- /dev/null
+++ b/internal_testsuites/starlark/service-directory/multiple-artifacts-directory/main.star
@@ -0,0 +1,36 @@
+SERVICE_NAME = "service-example"
+
+def run(plan, args={}):
+    artifact_name_1 = plan.upload_files("./folder-1/file-1.txt")
+    artifact_name_2 = plan.upload_files("./folder-2/file-2.txt")
+
+    plan.add_service(
+        SERVICE_NAME,
+        config=ServiceConfig(
+            image="alpine:latest",
+            cmd=["/bin/sh", "-c", "sleep 999"],
+            files={
+                "/files": Directory(
+                    artifact_names=[artifact_name_1,artifact_name_2]
+                )
+            },
+        ),
+    )
+
+    plan.exec(
+        service_name=SERVICE_NAME,
+        recipe=ExecRecipe([
+            "/bin/sh",
+            "-c",
+            "cat /files/file-1.txt",
+        ])
+    )
+
+    plan.exec(
+        service_name=SERVICE_NAME,
+        recipe=ExecRecipe([
+            "/bin/sh",
+            "-c",
+            "cat /files/file-2.txt",
+        ])
+    )