Repository: docker/compose-cli
Pull request #899 (2020-11-10 16:00:41)

Specific error message when specifying path as a volume source
Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>

**What I did**
add specific error message when user tries to specify a host path in compose file

**Related issue**
https://github.com/docker/compose-cli/issues/865

<!-- optional tests
You can add a / mention to run tests executed by default only on main branch : 
* `test-aci` to run ACI E2E tests
* `test-ecs` to run ECS E2E tests
* `test-windows` to run tests & E2E tests on windows
-->
/test-aci

**(not mandatory) A picture of a cute animal, if possible in relation with what you did**
![](https://miro.medium.com/max/600/0*_u8R30i-MKMXGkuW)


Patch:
diff --git a/aci/convert/convert.go b/aci/convert/convert.go
index f4479bd2..accd50ad 100644
--- a/aci/convert/convert.go
+++ b/aci/convert/convert.go
@@ -50,7 +50,7 @@ const (
 func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.Project, storageHelper login.StorageLogin) (containerinstance.ContainerGroup, error) {
 	project := projectAciHelper(p)
 	containerGroupName := strings.ToLower(project.Name)
-	volumesCache, volumesSlice, err := project.getAciFileVolumes(ctx, storageHelper)
+	volumesSlice, err := project.getAciFileVolumes(ctx, storageHelper)
 	if err != nil {
 		return containerinstance.ContainerGroup{}, err
 	}
@@ -90,7 +90,7 @@ func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.
 	var dnsLabelName *string
 	for _, s := range project.Services {
 		service := serviceConfigAciHelper(s)
-		containerDefinition, err := service.getAciContainer(volumesCache)
+		containerDefinition, err := service.getAciContainer()
 		if err != nil {
 			return containerinstance.ContainerGroup{}, err
 		}
@@ -162,8 +162,8 @@ type projectAciHelper types.Project
 
 type serviceConfigAciHelper types.ServiceConfig
 
-func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (containerinstance.Container, error) {
-	aciServiceVolumes, err := s.getAciFileVolumeMounts(volumesCache)
+func (s serviceConfigAciHelper) getAciContainer() (containerinstance.Container, error) {
+	aciServiceVolumes, err := s.getAciFileVolumeMounts()
 	if err != nil {
 		return containerinstance.Container{}, err
 	}
diff --git a/aci/convert/volume.go b/aci/convert/volume.go
index c1f2585c..51b6fd86 100644
--- a/aci/convert/volume.go
+++ b/aci/convert/volume.go
@@ -38,18 +38,17 @@ const (
 	volumeReadOnly                 = "read_only"
 )
 
-func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageLogin) (map[string]bool, []containerinstance.Volume, error) {
-	azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
+func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageLogin) ([]containerinstance.Volume, error) {
 	var azureFileVolumesSlice []containerinstance.Volume
 	for name, v := range p.Volumes {
 		if v.Driver == azureFileDriverName {
 			shareName, ok := v.DriverOpts[volumeDriveroptsShareNameKey]
 			if !ok {
-				return nil, nil, fmt.Errorf("cannot retrieve fileshare name for Azurefile")
+				return nil, fmt.Errorf("cannot retrieve fileshare name for Azurefile")
 			}
 			accountName, ok := v.DriverOpts[volumeDriveroptsAccountNameKey]
 			if !ok {
-				return nil, nil, fmt.Errorf("cannot retrieve account name for Azurefile")
+				return nil, fmt.Errorf("cannot retrieve account name for Azurefile")
 			}
 			readOnly, ok := v.DriverOpts[volumeReadOnly]
 			if !ok {
@@ -57,11 +56,11 @@ func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.St
 			}
 			ro, err := strconv.ParseBool(readOnly)
 			if err != nil {
-				return nil, nil, fmt.Errorf("invalid mode %q for volume", readOnly)
+				return nil, fmt.Errorf("invalid mode %q for volume", readOnly)
 			}
 			accountKey, err := helper.GetAzureStorageAccountKey(ctx, accountName)
 			if err != nil {
-				return nil, nil, err
+				return nil, err
 			}
 			aciVolume := containerinstance.Volume{
 				Name: to.StringPtr(name),
@@ -72,18 +71,17 @@ func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.St
 					ReadOnly:           &ro,
 				},
 			}
-			azureFileVolumesMap[name] = true
 			azureFileVolumesSlice = append(azureFileVolumesSlice, aciVolume)
 		}
 	}
-	return azureFileVolumesMap, azureFileVolumesSlice, nil
+	return azureFileVolumesSlice, nil
 }
 
-func (s serviceConfigAciHelper) getAciFileVolumeMounts(volumesCache map[string]bool) ([]containerinstance.VolumeMount, error) {
+func (s serviceConfigAciHelper) getAciFileVolumeMounts() ([]containerinstance.VolumeMount, error) {
 	var aciServiceVolumes []containerinstance.VolumeMount
 	for _, sv := range s.Volumes {
-		if !volumesCache[sv.Source] {
-			return []containerinstance.VolumeMount{}, fmt.Errorf("could not find volume source %q", sv.Source)
+		if sv.Type == string(types.VolumeTypeBind) {
+			return []containerinstance.VolumeMount{}, fmt.Errorf("host path (%q) not allowed as volume source, you need to reference an Azure File Share defined in the 'volumes' section", sv.Source)
 		}
 		aciServiceVolumes = append(aciServiceVolumes, containerinstance.VolumeMount{
 			Name:      to.StringPtr(sv.Source),
diff --git a/docs/aci-compose-features.md b/docs/aci-compose-features.md
index 1c7ba79b..7e96708a 100644
--- a/docs/aci-compose-features.md
+++ b/docs/aci-compose-features.md
@@ -47,7 +47,7 @@ __Legend:__
 | service.external_links         | x |
 | service.extra_hosts            | x |
 | service.group_add              | x |
-| service.healthcheck            | ✓ |
+| service.healthcheck            | ✓ | 
 | service.hostname               | x |
 | service.image                  | ✓ |  Private images will be accessible if the user is logged into the corresponding registry at deploy time. Users will be automatically logged in to Azure Container Registry using their Azure login if possible.
 | service.isolation              | x |
@@ -173,7 +173,7 @@ secrets:
     file: ./my_secret2.txt
 ```
 
-In this example the `nginx` service will have its secret mounted to `/run/secrets/renamedsecret1.txt` and `db` will have 2 files (`mysecretonmount1.txt` and `mysecretonmount2.txt`).
+In this example the `nginx` service will have its secret mounted in `/run/secrets/renamedsecret1.txt` and `db` will have 2 files (`mysecretonmount1.txt` and `mysecretonmount2.txt`). 
 Both of them with be mounted in the same folder (`/mnt/dbmount/`).
 
 
@@ -212,16 +212,12 @@ The web container will have its limits set to the same values as reservations, b
 
 ## Healthchecks
 
-A health check can be described in the `healthcheck` section of each service. This is translated to a `LivenessProbe` in ACI. If the health check fails then the container is considered unhealthy and terminated.
-In order for the container to be restarted automatically, the service needs to have a restart policy other than `none` to be set. Note that the default restart policy if one isn't set is `any`.
+Healthchecks can be described in the `healthcheck` section in the service. It translates to `LivenessProbe` in ACI. By that, the container is restarted if it becomes unhealthy.
 
 ```yaml
 services:
   web:
     image: nginx
-    deploy:
-      restart_policy:
-        condition: on-failure
     healthcheck:
       test: ["CMD", "curl", "-f", "http://localhost:80"]
       interval: 30s
@@ -230,4 +226,4 @@ services:
       start_period: 40s
 ```
 
-**Note:** that the `test` command can be a `string` or an array starting or not by `NONE`, `CMD`, `CMD-SHELL`. In the ACI implementation, these prefixes are ignored.
+**Note:** that the `test` command can be a `string` or an array starting or not by `NONE`, `CMD`, `CMD-SHELL`. In the ACI implementation, these prefixes are ignored.
\ No newline at end of file
diff --git a/docs/aci-container-features.md b/docs/aci-container-features.md
index e40b8d4a..5d8927b8 100644
--- a/docs/aci-container-features.md
+++ b/docs/aci-container-features.md
@@ -22,16 +22,11 @@ __Legend:__
 | --domainname          | ✓ | See [Exposing ports](#exposing-ports).
 | --e, --env            | ✓ | Sets environment variable.
 | --env-file            | ✓ | Sets environment variable from and external file.
-| --health-cmd          | ✓ | Specify healthcheck command. See [Healthchecks](#healthchecks).
-| --health-interval     | ✓ | Specify healthcheck interval
-| --health-retries      | ✓ | Specify healthcheck number of retries
-| --health-start-period | ✓ | Specify healthcheck initial delay
-| --health-timeout      | ✓ | Specify healthcheck timeout
 | -l, --label           | x | Unsupported in Docker ACI integration, due to limitations of ACI Tags.
 | -m, --memory          | ✓ | See [Container Resources](#container-resources).
 | --name                | ✓ | Provide a name for the container. Name must be unique withing the ACI resource group. a name is generated by default.
 | -p, --publish         | ✓ | See [Exposing ports](#exposing-ports). Only symetrical port mapping is supported in ACI.
-| --restart             | ✓ | Restart policy, must be one of: `always`, `no`, `on-failure`.
+| --restart             | ✓ | Restart policy, must be one of: `any`, `none`, `on-failure`.
 | --rm                  | x | Not supported as [ACI does not support auto-delete containers](https://feedback.azure.com/forums/602224-azure-container-instances/suggestions/34066633-support-auto-delete-of-aci-when-container-exits-no).
 | -v, --volume          | ✓ | See [Persistent Volumes](#persistent-volumes).
 
@@ -58,7 +53,7 @@ Credentials for storage accounts will be automatically fetched at deployment tim
 
 CPU and memory reservations can be set when running containers with `docker run --cpus 1.5 --memory 2G`.
 
-It is not possible to set resource limits that differ from resource reservation on single containers.
+It is not possible to set resource limits that differ from resource reservation on single containers. 
 ACI allows setting resource limits for containers in a container group but these limits must stay within the reserved resources for the entire group. In the case of a single container deployed in a container group, the resource limits must be equal to the resource reservation.
 
 ## Logs
@@ -68,10 +63,3 @@ You can view container logs with the command `docker logs <CONTAINER-ID>`.
 You can follow logs with the `--follow` (`-f`) option.
 When running a container with `docker run`, by default the command line stays attached to container logs when the container starts. Use `docker run --detach` to not follow logs once the container starts.
 > Note: Following ACI logs may have display issues especially when resizing a terminal that is following container logs. This is due to ACI providing raw log pulling but no streaming of logs. Logs are effectively pulled every 2 seconds when following logs.
-
-## Healthchecks
-
-A health check can be described using the flags prefixed by `--healthcheck`. This is translated into `LivenessProbe` for ACI. If the health check fails then the container is considered unhealthy and terminated.
-In order for the container to be restarted automatically, the container needs to be run with a restart policy (set by the `--restart` flag) other than `no`. Note that the default restart policy if one isn't set is `no`.
-
-In order to restart automatically, the container also need to have a restart policy set with `--restart` (`docker run` defaults to no restart policy)
diff --git a/docs/ecs-compose-features.md b/docs/ecs-compose-features.md
deleted file mode 100644
index 0fddf4c7..00000000
--- a/docs/ecs-compose-features.md
+++ /dev/null
@@ -1,176 +0,0 @@
-# Compose - Amazon ECS mapping
-
-This document outlines the conversion of an application defined in a Compose file to AWS resources.
-Each service is mapped to an ECS service in the project's cluster.
-
-## Compose fields mapping
-
-The table below lists supported Compose file fields and their AWS counterparts.
-
-__Legend:__
-
-- __✓:__ Implemented
-- __n:__ Not yet implemented
-- __x:__ Not applicable / no available conversion
-
-| Keys                           |Map|  Notes                                                       |
-|--------------------------------|---|--------------------------------------------------------------|
-| __Service__                    | ✓ |
-| service.service.build          | x |  Ignored. No image build support on AWS.
-| service.cap_add, cap_drop      | ✓ |  Supported with [Fargate limitations](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)
-| service.command                | ✓ |  
-| service.configs                | x |
-| service.cgroup_parent          | x |
-| service.container_name         | x |  
-| service.credential_spec        | x |
-| service.deploy                 | ✓ |
-| service.deploy.endpoint_mode   | x |
-| service.deploy.mode            | x |
-| service.deploy.replicas        | ✓ |  Set service initial scale. Auto-scaling, when enabled, will make this dynamic
-| service.deploy.placement       | ✓ |  Used with EC2 support to select a machine type and AMI
-| service.deploy.update_config   | ✓ |
-| service.deploy.resources       | ✓ |  Fargate resource is selected with the lowest instance type for configured memory and cpu
-| service.deploy.restart_policy  | ✓ |  
-| service.deploy.labels          | ✓ |  
-| service.devices                | x |
-| service.depends_on             | ✓ |  Implemented using CloudFormation Depends_on
-| service.dns                    | x |
-| service.dns_search             | x |
-| service.domainname             | x |  
-| service.tmpfs                  | x |  Not supported on Fargate, see https://github.com/docker/compose-cli/issues/839
-| service.entrypoint             | ✓ | 
-| service.env_file               | ✓ |
-| service.environment            | ✓ |
-| service.expose                 | x |
-| service.extends                | ✓ |
-| service.external_links         | x |
-| service.extra_hosts            | x |
-| service.group_add              | x |
-| service.healthcheck            | ✓ |  This configures container level health check as reported on ECS console. Application Load Balancer will also check for HTTP service health by accessing `/` and expect a HTTP 200 status code.
-| service.hostname               | x |
-| service.image                  | ✓ |  Private images will be accessible by passing x-aws-pull_policy with ARN of a username+password secret
-| service.isolation              | x |
-| service.labels                 | x |  
-| service.links                  | x |
-| service.logging                | ✓ |  Can be used to customize CloudWatch Logs configuration
-| service.network_mode           | x |
-| service.networks               | x |  Communication between services is implemented by SecurityGroups within the application VPC.
-| service.pid                    | x |
-| service.ports                  | ✓ |  Only symetrical port mapping is supported in ECS. See [Exposing ports](#exposing-ports).
-| service.secrets                | ✓ |  See [Secrets](#secrets).
-| service.security_opt           | x |
-| service.stop_grace_period      | x |
-| service.stop_signal            | x |
-| service.sysctls                | x |
-| service.ulimits                | ✓ |  Only support `nofile` ulimit due to Fargate limitations
-| service.userns_mode            | x |
-| service.volumes                | ✓ |  Mapped to EFS File Systems. See [Persistent volumes](#persistent-volumes).
-| service.restart                | x |  Replaced by service.deployment.restart_policy
-|                                |   |
-| __Volume__                     | x |
-| driver                         | ✓ |  See [Persistent volumes](#persistent-volumes).
-| driver_opts                    | ✓ |
-| external                       | ✓ |  `name` must be an EFS filesystem ID
-| labels                         | x |
-|                                |   |
-| __Secret__                     | x |
-| external                       | ✓ |  `name` must be set to secret's ARN
-| file                           | ✓ |  file content will be uploaded into AWS Secret Manager
-|                                |   |
-| __Config__                     | x |
-|                                |   |
-
-
-## Logs
-
-Application logs can be obtained container with `docker compose logs`.
-The Docker ECS integration relies on AWS CloudWatch Logs to collect logs from all containers. CloudWatch can be customized by setting service `logging.driver_opts`
-by passing configuration attributes prefixed with `awslogs-`.
-
-```yaml
-  test:
-    image: mycompany/webapp
-    logging:
-      driver-opts:
-        awslogs-datetime-pattern: "some-pattern"
-```
-
-
-## Exposing ports
-
-When one or more services expose ports, a Load Balancer is created for the application.
-As all services are expose through the same Load Balancer, only one service can expose a given port number.
-The source and target ports defined in the Compose file MUST be the same, as service-to-service communication don't go trought the Load Balancer and could not
-benefit from Listeners abstraction to assign a distinct published port. 
-
-If services in the Compose file only expose ports 80 or 443, an Application Load Balancer is created, otherwise ECS integration will provision a Network Load Balancer.
-HTTP services using distinct ports can force use of an ALB by claiming the http protocol with `x-aws-protocol` custom extension within the port declaration:
-
-```yaml
-  test:
-    image: mycompany/webapp
-    ports:
-      - target: 8080
-        x-aws-protocol: http
-
-```
-
-## Persistent volumes
-
-Docker volumes are mapped to EFS file systems. Volumes can be external (`name` must then be set to filesystem ID) or will be created when the application is
-first deployed. `docker compose down` will NOT delete the filesystem, and it will be re-attached to the application on future runs.
-`driver_opts` can be used to tweak the EFS filsystem.
-
-Volume mount can be customized to workaround Posix filesystem permission issues by setting user and group IDs to be used to write to filesystem, whatever user
-is configured to run the container.
-
-```yaml
-services:
-    myservice:
-        image: mycompany/webapp
-        volumes:
-        - mydata:/mount/testvolumes
-
-volumes:
-  mydata:
-    driver_opts:
-      performance-mode: maxIO 
-      throughput-mode: bursting
-      uid: 0
-      gid: 0
-```
-
-
-## Secrets
-
-Secrets can be defined in compose files, and will need secret files available at deploy time next to the compose file.
-The content of the secret file will be made available inside selected containers, by default under `/run/secrets/<SECRET_NAME>`.
-External secrets are also supported, `name` must then be set to secret's ARN
-
-```yaml
-services:
-    nginx:
-        image: mycompany/webapp
-        secrets:
-          - mysecret
-
-secrets:
-  mysecret:
-    file: ./my_secret1.txt
-```
-
-
-## Container Resources
-
-CPU and memory limits can be set in compose. Those are used to select the minimal [Fargate size](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html) that will match those limits.
-
-```yaml
-services:
-    nginx:
-        image: mycompany/webapp
-        deploy:
-          resources:
-            limits:
-              cpu: 0.5
-              memory: 2Gb
-```          
diff --git a/ecs/aws.go b/ecs/aws.go
index 782c25c8..b030c7aa 100644
--- a/ecs/aws.go
+++ b/ecs/aws.go
@@ -42,8 +42,8 @@ type API interface {
 	GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error)
 	GetRoleArn(ctx context.Context, name string) (string, error)
 	StackExists(ctx context.Context, name string) (bool, error)
-	CreateStack(ctx context.Context, name string, region string, template []byte) error
-	CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error)
+	CreateStack(ctx context.Context, name string, template []byte) error
+	CreateChangeSet(ctx context.Context, name string, template []byte) (string, error)
 	UpdateStack(ctx context.Context, changeset string) error
 	WaitStackComplete(ctx context.Context, name string, operation int) error
 	GetStackID(ctx context.Context, name string) (string, error)
diff --git a/ecs/aws_mock.go b/ecs/aws_mock.go
index 428bc8c8..bfdaed14 100644
--- a/ecs/aws_mock.go
+++ b/ecs/aws_mock.go
@@ -66,18 +66,18 @@ func (mr *MockAPIMockRecorder) CheckVPC(arg0, arg1 interface{}) *gomock.Call {
 }
 
 // CreateChangeSet mocks base method
-func (m *MockAPI) CreateChangeSet(arg0 context.Context, arg1, arg2 string, arg3 []byte) (string, error) {
+func (m *MockAPI) CreateChangeSet(arg0 context.Context, arg1 string, arg2 []byte) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateChangeSet", arg0, arg1, arg2, arg3)
+	ret := m.ctrl.Call(m, "CreateChangeSet", arg0, arg1, arg2)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // CreateChangeSet indicates an expected call of CreateChangeSet
-func (mr *MockAPIMockRecorder) CreateChangeSet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+func (mr *MockAPIMockRecorder) CreateChangeSet(arg0, arg1, arg2 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChangeSet", reflect.TypeOf((*MockAPI)(nil).CreateChangeSet), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChangeSet", reflect.TypeOf((*MockAPI)(nil).CreateChangeSet), arg0, arg1, arg2)
 }
 
 // CreateCluster mocks base method
@@ -126,17 +126,17 @@ func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1 interface{}) *gomock.Call
 }
 
 // CreateStack mocks base method
-func (m *MockAPI) CreateStack(arg0 context.Context, arg1, arg2 string, arg3 []byte) error {
+func (m *MockAPI) CreateStack(arg0 context.Context, arg1 string, arg2 []byte) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateStack", arg0, arg1, arg2, arg3)
+	ret := m.ctrl.Call(m, "CreateStack", arg0, arg1, arg2)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // CreateStack indicates an expected call of CreateStack
-func (mr *MockAPIMockRecorder) CreateStack(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+func (mr *MockAPIMockRecorder) CreateStack(arg0, arg1, arg2 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStack", reflect.TypeOf((*MockAPI)(nil).CreateStack), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStack", reflect.TypeOf((*MockAPI)(nil).CreateStack), arg0, arg1, arg2)
 }
 
 // DeleteAutoscalingGroup mocks base method
diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go
index 0c52fba2..a28c48f9 100644
--- a/ecs/cloudformation.go
+++ b/ecs/cloudformation.go
@@ -385,13 +385,13 @@ func (b *ecsAPIService) createTaskRole(project *types.Project, service types.Ser
 	rolePolicies := []iam.Role_Policy{}
 	if roles, ok := service.Extensions[extensionRole]; ok {
 		rolePolicies = append(rolePolicies, iam.Role_Policy{
-			PolicyName:     fmt.Sprintf("%sPolicy", normalizeResourceName(service.Name)),
+			PolicyName:     fmt.Sprintf("%s%sPolicy", normalizeResourceName(project.Name), normalizeResourceName(service.Name)),
 			PolicyDocument: roles,
 		})
 	}
 	for _, vol := range service.Volumes {
 		rolePolicies = append(rolePolicies, iam.Role_Policy{
-			PolicyName:     fmt.Sprintf("%s%sVolumeMountPolicy", normalizeResourceName(service.Name), normalizeResourceName(vol.Source)),
+			PolicyName:     fmt.Sprintf("%s%sVolumeMountPolicy", normalizeResourceName(project.Name), normalizeResourceName(service.Name)),
 			PolicyDocument: volumeMountPolicyDocument(vol.Source, resources.filesystems[vol.Source].ARN()),
 		})
 	}
diff --git a/ecs/compatibility.go b/ecs/compatibility.go
index d79d3849..5b2e7118 100644
--- a/ecs/compatibility.go
+++ b/ecs/compatibility.go
@@ -27,10 +27,9 @@ import (
 
 func (b *ecsAPIService) checkCompatibility(project *types.Project) error {
 	var checker compatibility.Checker = &fargateCompatibilityChecker{
-		AllowList: compatibility.AllowList{
+		compatibility.AllowList{
 			Supported: compatibleComposeAttributes,
 		},
-		projet: project,
 	}
 	compatibility.Check(project, checker)
 	for _, err := range checker.Errors() {
@@ -47,7 +46,6 @@ func (b *ecsAPIService) checkCompatibility(project *types.Project) error {
 
 type fargateCompatibilityChecker struct {
 	compatibility.AllowList
-	projet *types.Project
 }
 
 var compatibleComposeAttributes = []string{
@@ -93,6 +91,7 @@ var compatibleComposeAttributes = []string{
 	"services.user",
 	"services.volumes",
 	"services.volumes.read_only",
+	"services.volumes.source",
 	"services.volumes.target",
 	"services.working_dir",
 	"secrets.external",
@@ -121,15 +120,6 @@ func (c *fargateCompatibilityChecker) CheckPortsPublished(p *types.ServicePortCo
 	}
 }
 
-func (c *fargateCompatibilityChecker) CheckVolumesSource(config *types.ServiceVolumeConfig) {
-	if config.Type == types.VolumeTypeBind {
-		c.Incompatible("ECS Fargate does not support bind mounts from host")
-	}
-	if config.Type == types.VolumeTypeTmpfs {
-		c.Incompatible("ECS Fargate does not support tmpfs")
-	}
-}
-
 func (c *fargateCompatibilityChecker) CheckCapAdd(service *types.ServiceConfig) {
 	add := []string{}
 	for _, cap := range service.CapAdd {
diff --git a/ecs/sdk.go b/ecs/sdk.go
index d12e6171..6709a621 100644
--- a/ecs/sdk.go
+++ b/ecs/sdk.go
@@ -17,7 +17,6 @@
 package ecs
 
 import (
-	"bytes"
 	"context"
 	"encoding/json"
 	"fmt"
@@ -31,7 +30,6 @@ import (
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/arn"
-	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/aws/request"
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/autoscaling"
@@ -50,32 +48,26 @@ import (
 	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
 	"github.com/aws/aws-sdk-go/service/iam"
 	"github.com/aws/aws-sdk-go/service/iam/iamiface"
-	"github.com/aws/aws-sdk-go/service/s3"
-	"github.com/aws/aws-sdk-go/service/s3/s3iface"
-	"github.com/aws/aws-sdk-go/service/s3/s3manager"
 	"github.com/aws/aws-sdk-go/service/secretsmanager"
 	"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
 	"github.com/hashicorp/go-multierror"
-	"github.com/hashicorp/go-uuid"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
 type sdk struct {
-	ECS      ecsiface.ECSAPI
-	EC2      ec2iface.EC2API
-	EFS      efsiface.EFSAPI
-	ELB      elbv2iface.ELBV2API
-	CW       cloudwatchlogsiface.CloudWatchLogsAPI
-	IAM      iamiface.IAMAPI
-	CF       cloudformationiface.CloudFormationAPI
-	SM       secretsmanageriface.SecretsManagerAPI
-	SSM      ssmiface.SSMAPI
-	AG       autoscalingiface.AutoScalingAPI
-	S3       s3iface.S3API
-	uploader *s3manager.Uploader
+	ECS ecsiface.ECSAPI
+	EC2 ec2iface.EC2API
+	EFS efsiface.EFSAPI
+	ELB elbv2iface.ELBV2API
+	CW  cloudwatchlogsiface.CloudWatchLogsAPI
+	IAM iamiface.IAMAPI
+	CF  cloudformationiface.CloudFormationAPI
+	SM  secretsmanageriface.SecretsManagerAPI
+	SSM ssmiface.SSMAPI
+	AG  autoscalingiface.AutoScalingAPI
 }
 
 // sdk implement API
@@ -86,18 +78,16 @@ func newSDK(sess *session.Session) sdk {
 		request.AddToUserAgent(r, internal.ECSUserAgentName+"/"+internal.Version)
 	})
 	return sdk{
-		ECS:      ecs.New(sess),
-		EC2:      ec2.New(sess),
-		EFS:      efs.New(sess),
-		ELB:      elbv2.New(sess),
-		CW:       cloudwatchlogs.New(sess),
-		IAM:      iam.New(sess),
-		CF:       cloudformation.New(sess),
-		SM:       secretsmanager.New(sess),
-		SSM:      ssm.New(sess),
-		AG:       autoscaling.New(sess),
-		S3:       s3.New(sess),
-		uploader: s3manager.NewUploader(sess),
+		ECS: ecs.New(sess),
+		EC2: ec2.New(sess),
+		EFS: efs.New(sess),
+		ELB: elbv2.New(sess),
+		CW:  cloudwatchlogs.New(sess),
+		IAM: iam.New(sess),
+		CF:  cloudformation.New(sess),
+		SM:  secretsmanager.New(sess),
+		SSM: ssm.New(sess),
+		AG:  autoscaling.New(sess),
 	}
 }
 
@@ -197,9 +187,11 @@ func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error
 			return nil, err
 		}
 		for _, subnet := range subnets.Subnets {
+			id := aws.StringValue(subnet.SubnetId)
+			logrus.Debugf("Found SubNet %s", id)
 			ids = append(ids, existingAWSResource{
 				arn: aws.StringValue(subnet.SubnetArn),
-				id:  aws.StringValue(subnet.SubnetId),
+				id:  id,
 			})
 		}
 
@@ -234,119 +226,39 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
 	return len(stacks.Stacks) > 0, nil
 }
 
-type uploadedTemplateFunc func(body *string, url *string) (string, error)
-
-const cloudformationBytesLimit = 51200
-
-func (s sdk) withTemplate(ctx context.Context, name string, template []byte, region string, fn uploadedTemplateFunc) (string, error) {
-	if len(template) < cloudformationBytesLimit {
-		return fn(aws.String(string(template)), nil)
-	}
-
-	logrus.Debug("Create s3 bucket to store cloudformation template")
-	var configuration *s3.CreateBucketConfiguration
-	if region != "us-east-1" {
-		configuration = &s3.CreateBucketConfiguration{
-			LocationConstraint: aws.String(region),
-		}
-	}
-	// CloudFormation will only allow URL from a same-region bucket
-	// to avoid conflicts we suffix bucket name by region, so we can create comparable buckets in other regions.
-	bucket := "com.docker.compose." + region
-	_, err := s.S3.CreateBucket(&s3.CreateBucketInput{
-		Bucket:                    aws.String(bucket),
-		CreateBucketConfiguration: configuration,
-	})
-	if err != nil {
-		ae, ok := err.(awserr.Error)
-		if !ok {
-			return "", err
-		}
-		if ae.Code() != s3.ErrCodeBucketAlreadyOwnedByYou {
-			return "", err
-		}
-	}
-
-	key, err := uuid.GenerateUUID()
-	if err != nil {
-		return "", err
-	}
-
-	upload, err := s.uploader.UploadWithContext(ctx, &s3manager.UploadInput{
-		Key:         aws.String(key),
-		Body:        bytes.NewReader(template),
-		Bucket:      aws.String(bucket),
-		ContentType: aws.String("application/json"),
-		Tagging:     aws.String(name),
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	defer s.S3.DeleteObjects(&s3.DeleteObjectsInput{ //nolint: errcheck
-		Bucket: aws.String(bucket),
-		Delete: &s3.Delete{
-			Objects: []*s3.ObjectIdentifier{
-				{
-					Key:       aws.String(key),
-					VersionId: upload.VersionID,
-				},
-			},
-		},
-	})
-
-	return fn(nil, aws.String(upload.Location))
-}
-
-func (s sdk) CreateStack(ctx context.Context, name string, region string, template []byte) error {
+func (s sdk) CreateStack(ctx context.Context, name string, template []byte) error {
 	logrus.Debug("Create CloudFormation stack")
 
-	stackID, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) {
-		stack, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
-			OnFailure:        aws.String("DELETE"),
-			StackName:        aws.String(name),
-			TemplateBody:     body,
-			TemplateURL:      url,
-			TimeoutInMinutes: nil,
-			Capabilities: []*string{
-				aws.String(cloudformation.CapabilityCapabilityIam),
-			},
-			Tags: []*cloudformation.Tag{
-				{
-					Key:   aws.String(compose.ProjectTag),
-					Value: aws.String(name),
-				},
+	_, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
+		OnFailure:        aws.String("DELETE"),
+		StackName:        aws.String(name),
+		TemplateBody:     aws.String(string(template)),
+		TimeoutInMinutes: nil,
+		Capabilities: []*string{
+			aws.String(cloudformation.CapabilityCapabilityIam),
+		},
+		Tags: []*cloudformation.Tag{
+			{
+				Key:   aws.String(compose.ProjectTag),
+				Value: aws.String(name),
 			},
-		})
-		if err != nil {
-			return "", err
-		}
-		return aws.StringValue(stack.StackId), nil
+		},
 	})
-	logrus.Debugf("Stack %s created", stackID)
 	return err
 }
 
-func (s sdk) CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error) {
+func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte) (string, error) {
 	logrus.Debug("Create CloudFormation Changeset")
-	update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05"))
 
-	changeset, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) {
-		changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
-			ChangeSetName: aws.String(update),
-			ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
-			StackName:     aws.String(name),
-			TemplateBody:  body,
-			TemplateURL:   url,
-			Capabilities: []*string{
-				aws.String(cloudformation.CapabilityCapabilityIam),
-			},
-		})
-		if err != nil {
-			return "", err
-		}
-		return aws.StringValue(changeset.Id), err
+	update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05"))
+	changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
+		ChangeSetName: aws.String(update),
+		ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
+		StackName:     aws.String(name),
+		TemplateBody:  aws.String(string(template)),
+		Capabilities: []*string{
+			aws.String(cloudformation.CapabilityCapabilityIam),
+		},
 	})
 	if err != nil {
 		return "", err
@@ -355,7 +267,7 @@ func (s sdk) CreateChangeSet(ctx context.Context, name string, region string, te
 	// we have to WaitUntilChangeSetCreateComplete even this in fail with error `ResourceNotReady`
 	// so that we can invoke DescribeChangeSet to check status, and then we can know about the actual creation failure cause.
 	s.CF.WaitUntilChangeSetCreateCompleteWithContext(ctx, &cloudformation.DescribeChangeSetInput{ // nolint:errcheck
-		ChangeSetName: aws.String(changeset),
+		ChangeSetName: changeset.Id,
 	})
 
 	desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{
@@ -363,10 +275,10 @@ func (s sdk) CreateChangeSet(ctx context.Context, name string, region string, te
 		StackName:     aws.String(name),
 	})
 	if aws.StringValue(desc.Status) == "FAILED" {
-		return changeset, fmt.Errorf(aws.StringValue(desc.StatusReason))
+		return *changeset.Id, fmt.Errorf(aws.StringValue(desc.StatusReason))
 	}
 
-	return changeset, err
+	return *changeset.Id, err
 }
 
 func (s sdk) UpdateStack(ctx context.Context, changeset string) error {
diff --git a/ecs/up.go b/ecs/up.go
index 373407e2..308683e8 100644
--- a/ecs/up.go
+++ b/ecs/up.go
@@ -44,7 +44,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach b
 	operation := stackCreate
 	if update {
 		operation = stackUpdate
-		changeset, err := b.aws.CreateChangeSet(ctx, project.Name, b.Region, template)
+		changeset, err := b.aws.CreateChangeSet(ctx, project.Name, template)
 		if err != nil {
 			return err
 		}
@@ -53,7 +53,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach b
 			return err
 		}
 	} else {
-		err = b.aws.CreateStack(ctx, project.Name, b.Region, template)
+		err = b.aws.CreateStack(ctx, project.Name, template)
 		if err != nil {
 			return err
 		}
diff --git a/go.mod b/go.mod
index 1e05d104..ba7c297c 100644
--- a/go.mod
+++ b/go.mod
@@ -40,7 +40,6 @@ require (
 	github.com/google/uuid v1.1.2
 	github.com/gorilla/mux v1.7.4 // indirect
 	github.com/hashicorp/go-multierror v1.1.0
-	github.com/hashicorp/go-uuid v1.0.1
 	github.com/iancoleman/strcase v0.1.2
 	github.com/joho/godotenv v1.3.0
 	github.com/labstack/echo v3.3.10+incompatible
diff --git a/go.sum b/go.sum
index fb7ab35a..14e8633a 100644
--- a/go.sum
+++ b/go.sum
@@ -298,7 +298,6 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=


Test patch:
diff --git a/aci/convert/volume_test.go b/aci/convert/volume_test.go
index 941ed784..36062bf7 100644
--- a/aci/convert/volume_test.go
+++ b/aci/convert/volume_test.go
@@ -119,6 +119,30 @@ func TestComposeVolumes(t *testing.T) {
 	assert.DeepEqual(t, (*group.Volumes)[0], expectedGroupVolume)
 }
 
+func TestPathVolumeErrorMessage(t *testing.T) {
+	ctx := context.TODO()
+	accountName := "myAccount"
+	mockStorageHelper.On("GetAzureStorageAccountKey", ctx, accountName).Return("123456", nil)
+	project := types.Project{
+		Services: []types.ServiceConfig{
+			{
+				Name:  "service1",
+				Image: "image1",
+				Volumes: []types.ServiceVolumeConfig{
+					{
+						Source: "/path",
+						Target: "/target",
+						Type:   string(types.VolumeTypeBind),
+					},
+				},
+			},
+		},
+	}
+
+	_, err := ToContainerGroup(ctx, convertCtx, project, mockStorageHelper)
+	assert.ErrorContains(t, err, `host path ("/path") not allowed as volume source, you need to reference an Azure File Share defined in the 'volumes' section`)
+}
+
 func TestComposeVolumesRO(t *testing.T) {
 	ctx := context.TODO()
 	accountName := "myAccount"