I tried using unsupported ARM64 / FARGATE_SPOT / ECS Exec in ECS Express Mode
This page has been translated by machine translation. View original
ECS Express Mode is a convenient feature that allows you to quickly build environments with minimal configuration, but it doesn't allow you to use features such as ARM64 (Graviton), Fargate Spot, and ECS Exec through initial configuration.
This time, I had an opportunity to try enabling these features in a development environment using ECS Express Mode to improve cost and debugging efficiency, so I'd like to introduce the procedure.
Challenge: Express Mode Constraints and Workarounds
ECS Express Mode (AWS::ECS::ExpressGatewayService) is a high-level component that automatically generates and manages low-level resources such as task definitions, services, ALB, and Auto Scaling internally. Therefore, properties like runtimePlatform and capacityProviderStrategy that can be specified in normal CloudFormation do not exist in Express Mode templates.
The architecture is fixed to x86_64 and cannot be changed through the update-express-gateway-service API.
To work around this constraint, we implemented a two-stage deployment:
- Dummy deployment: Create a stack with an x86 dummy image having the same port and path as production.
- Architecture conversion: Edit the auto-generated task definition to ARM64 using CLI and apply it with
update-service.
Step 1: Creating the Initial Stack with CloudFormation
First, we successfully deployed resources managed by Express Mode using an x86 dummy image.
Verification Code (CloudFormation Template)
- IAM for SSM was set up in the initial template to use ECS Exec.
Full Initial Template
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ECS Express Mode - Initial Deployment (x86 Dummy)'
Parameters:
ServiceName:
Type: String
Default: 'my-app-dev'
InitialStackName:
Type: String
Default: 'ecs-express-initial'
Resources:
ECSLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/ecs/default/${ServiceName}-service'
RetentionInDays: 14
TaskRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${ServiceName}-task-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: ECSExecPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ssmmessages:CreateControlChannel
- ssmmessages:CreateDataChannel
- ssmmessages:OpenControlChannel
- ssmmessages:OpenDataChannel
Resource: '*'
ExpressModeService:
Type: AWS::ECS::ExpressGatewayService
DependsOn: ECSLogGroup
Properties:
ServiceName: !Sub '${ServiceName}-service'
Cluster: 'default'
ExecutionRoleArn:
Fn::ImportValue: !Sub '${InitialStackName}-TaskExecutionRoleArn'
InfrastructureRoleArn:
Fn::ImportValue: !Sub '${InitialStackName}-InfrastructureRoleArn'
TaskRoleArn: !GetAtt TaskRole.Arn
PrimaryContainer:
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-app:dummy'
ContainerPort: 8000
AwsLogsConfiguration:
LogGroup: !Sub '/aws/ecs/default/${ServiceName}-service'
LogStreamPrefix: 'ecs'
NetworkConfiguration:
Subnets:
Fn::Split:
- ','
- Fn::ImportValue: !Sub '${InitialStackName}-PrivateSubnets'
Cpu: '256'
Memory: '512'
HealthCheckPath: '/health'
ScalingTarget:
MinTaskCount: 1
MaxTaskCount: 1
AutoScalingMetric: 'AVERAGE_CPU'
AutoScalingTargetValue: 70
Outputs:
ServiceEndpoint:
Value: !GetAtt ExpressModeService.Endpoint
We deployed this template and confirmed that the task definition's runtimePlatform was started with the default x86_64.
Step 2: Converting Task Definition to ARM64 and Applying
We retrieved the task definition revision automatically generated by Express Mode, replaced the cpuArchitecture with ARM64, and the image with the production ARM image.
SERVICE_NAME="my-app-dev-service"
TD_FAMILY="default-${SERVICE_NAME}"
REGION="ap-northeast-1"
ARM_IMAGE="123456789012.dkr.ecr.$REGION.amazonaws.com/my-app:latest"
# 1. Retrieve current task definition
LATEST=$(aws ecs list-task-definitions --family-prefix $TD_FAMILY --sort DESC \
--region $REGION --query 'taskDefinitionArns[0]' --output text)
aws ecs describe-task-definition --task-definition $LATEST \
--region $REGION --query 'taskDefinition' > /tmp/td.json
# 2. Change to ARM64 + production image (including metadata removal)
jq --arg img "$ARM_IMAGE" '
.runtimePlatform.cpuArchitecture = "ARM64" |
.containerDefinitions[0].image = $img |
.cpu = "512" | .memory = "2048" |
del(.taskDefinitionArn, .revision, .status, .requiresAttributes,
.compatibilities, .registeredAt, .registeredBy)
' /tmp/td.json > /tmp/td_arm.json
# 3. Register new revision
aws ecs register-task-definition \
--cli-input-json file:///tmp/td_arm.json \
--region $REGION
# 4. Update service
NEW_REV=$(aws ecs list-task-definitions --family-prefix $TD_FAMILY --sort DESC \
--region $REGION --query 'taskDefinitionArns[0]' --output text)
aws ecs update-service \
--cluster default \
--service $SERVICE_NAME \
--task-definition $NEW_REV \
--force-new-deployment \
--region $REGION
Behavior Check During update-stack Execution
After switching to ARM64 using CLI, we executed CloudFormation's update-stack and changed the image setting in the template.
- A new task definition revision was generated by the
update-stackexecution. - We confirmed that the
cpuArchitecturein theruntimePlatformwas maintained asARM64in the new revision. - We confirmed that the image specified in the template was reflected in the new revision.
We were able to confirm that during Express Mode stack updates, the architecture settings from the currently running task definition are inherited.
Step 3: Enabling FARGATE_SPOT and ECS Exec
After converting to ARM64, we further enabled cost optimization and debugging features.
These were applied using aws ecs update-service.
aws ecs update-service \
--cluster default \
--service my-app-dev-service \
--capacity-provider-strategy capacityProvider=FARGATE_SPOT,base=0,weight=1 \
--enable-execute-command \
--propagate-tags TASK_DEFINITION \
--force-new-deployment \
--region ap-northeast-1
Verification
After applying the settings, we logged into the container using ECS Exec and checked the architecture.
# Executed inside the container
$ uname -m
aarch64
It displayed aarch64, confirming that it was running on ARM64 (Graviton). We also confirmed in the management console that the capacity provider strategy was set to FARGATE_SPOT in the service settings.
| Feature | Default | Enhanced Setting | Method |
|---|---|---|---|
| Arch | x86_64 | ARM64 (Graviton) | Task definition re-registration |
| Purchase Model | FARGATE | FARGATE_SPOT | update-service (CLI) |
| Debugging | Disabled | ECS Exec Enabled | IAM grant + update-service |
Conclusion
Even with ECS Express Mode, we were able to utilize ARM64, Fargate Spot, and ECS Exec through a two-stage deployment process.
However, as this procedure is not explicitly documented in official documentation at this time, it's possible that behavior may change with future updates, or issues such as interference from changes outside of IaC or drift may occur. We recommend limiting use to development and testing environments where you can take responsibility for the consequences.