I tried using unsupported ARM64 / FARGATE_SPOT / ECS Exec in ECS Express Mode

I tried using unsupported ARM64 / FARGATE_SPOT / ECS Exec in ECS Express Mode

Explaining a two-stage deployment process to bypass ECS Express Mode limitations and subsequently enable ARM64 (Graviton), Fargate Spot, and ECS Exec. This article introduces implementation procedures combining CloudFormation and AWS CLI.
2026.02.11

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:

  1. Dummy deployment: Create a stack with an x86 dummy image having the same port and path as production.
  2. 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-stack execution.
  • We confirmed that the cpuArchitecture in the runtimePlatform was maintained as ARM64 in 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.

Share this article

FacebookHatena blogX