ECS Express ModeをTerraformで触ってみる。
はじめに
皆様こんにちは、あかいけです。
ECSでWebアプリケーションをデプロイするとき、設定しなければならないリソースが意外と多く、ちょっと面倒だなと思ったことはありませんか?
私はあります。
2025/11/21にリリースされたECS Express Modeはそんなお悩みを解決してくれます。
なんと2つのIAMロールとコンテナイメージを指定するだけで、その他のリソースの設定を全部やってくれるという夢のような機能です。
そしてありがたいことに、terraform-provider-aws v6.23.0でaws_ecs_express_gateway_serviceリソースが追加され、TerraformでもExpress Modeを扱えるようになりました。
今までTerraformで細々とECS環境を構築してきた私としては、これは試さずにはいられません。
というわけで今回は、
遅ればせながらTerraformでECS Express Modeを触ってみた内容をまとめます。
なお記事内のコードではECS Express Mode関連のリソースのみ記述しています。
コードの全量は以下リポジトリへ格納しているので、ご自由にご利用ください。
ECS Express Modeについて
従来のECSサービスでは、ALB、ターゲットグループ、セキュリティグループ、オートスケーリングポリシーなど、多くのリソースを個別に設定する必要がありました。
正直、Webアプリケーションを動かしたいだけなのに、インフラ周りの設定で結構な時間を取られた経験は誰しもあるのではないでしょうか。
しかしECS Express Modeでは、
なんと以下の3つを指定するだけで本番環境レベルのインフラが自動構成されます。
- コンテナイメージ
- タスク実行ロール
- インフラストラクチャロール
自動で作成されるリソース
「3つ指定するだけ」と言われても、裏で何が作られているのか気になりますよね。
ECS Express Modeを使うと、以下のリソースが自動で作成・構成されます。
- ECSクラスター
- Fargateベースのタスク定義
- ECSサービス(カナリアデプロイメント)
- ALB(SSL/TLS終端、HTTPSリスナー)
- ターゲットグループ
- セキュリティグループ
- オートスケーリング設定
- CloudWatch Logsロググループ
- ACMパブリック証明書
並べてみると、めちゃくちゃ多いですね…。
これをTerraformで定義するとかなりの行数になるので、自動でやってくれるのはかなりありがたいです。
料金
ECS Express Mode自体の追加料金はありません。
作成されるAWSリソース(Fargate、ALB、CloudWatch Logs、データ転送など)の料金のみ発生します。
また、ALBは最大25個のExpress Modeサービスで共有されるため、コスト効率も良さそうです。
複数のサービスを立てる場合、ALBのコストが分散されるのは嬉しいポイントですね。
Application Load Balancer の共有 – 作成された ALB は、ホストヘッダーベースのリスナールールを使用して最大 25 の ECS サービス間で自動的に共有されます。これにより、ALB のコストを大幅に分散できます。
最小限の設定で作ってみる
まずは最小限の設定でECS Express Modeをデプロイしてみます。
どれくらいシンプルに書けるのか、見てみましょう。
IAMロールの作成
ECS Express Modeには2つのIAMロールが必要です。
- タスク実行ロール
- コンテナイメージのプルやCloudWatch Logsへのログ出力に使用される
- インフラストラクチャロール
- ALBやセキュリティグループなどのインフラリソースの作成・管理に使用される
- Express Mode用のマネージドポリシー:
AmazonECSInfrastructureRolePolicyForExpressAccess
ECS Express Modeサービスの作成
さて、ここからが本題です。
最小構成のTerraformコードはこちら。
resource "aws_ecs_express_gateway_service" "example" {
execution_role_arn = aws_iam_role.execution.arn
infrastructure_role_arn = aws_iam_role.infrastructure.arn
primary_container {
image = "httpd:latest"
}
}
…え、これだけ?と思いますよね。
従来のECS + ALB構成をTerraformで書こうとすると、aws_ecs_cluster、aws_ecs_task_definition、aws_ecs_service、aws_lb、aws_lb_target_group、aws_lb_listener、aws_security_group…etc と、かなりの量になるので、私も最初は疑いました。
デプロイしてみる
では実際にデプロイしてみます。
aws_iam_role.execution: Creating...
aws_iam_role.infrastructure: Creating...
aws_iam_role.execution: Creation complete after 2s [id=ecs-express-task-execution-role]
aws_iam_role_policy_attachment.execution: Creating...
aws_iam_role.infrastructure: Creation complete after 2s [id=ecs-express-infrastructure-role]
aws_iam_role_policy_attachment.infrastructure: Creating...
aws_ecs_express_gateway_service.example: Creating...
aws_iam_role_policy_attachment.execution: Creation complete after 1s [id=ecs-express-task-execution-role/arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy]
aws_iam_role_policy_attachment.infrastructure: Creation complete after 1s [id=ecs-express-infrastructure-role/arn:aws:iam::aws:policy/service-role/AmazonECSInfrastructureRoleforExpressGatewayServices]
aws_ecs_express_gateway_service.example: Still creating... [10s elapsed]
aws_ecs_express_gateway_service.example: Creation complete after 11s
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Terraformのapply自体は10秒ちょっとで完了しました。
ただし、裏でAWSリソース(ALB、ターゲットグループ、ECSサービスなど)が作成されるので、実際にアクセスできるようになるまでは大体6分ぐらいかかりました。
| タイムライン | リソース一覧 |
|---|---|
![]() |
![]() |
またAWS提供のエンドポイント(<service-name>.ecs.<region>.on.aws形式)とACMパブリック証明書も合わせて作成されるため、ドメインを用意しなくても、すぐにHTTPSでアクセスできるのは手軽で良いですね。
(かなり見えにくいですが、以下はhttpdのデフォルトページです)

イメージを入れ替えてみる
次に、コンテナイメージを変更してみます。
ここではNginxに入れ替えてみました。
resource "aws_ecs_express_gateway_service" "example" {
execution_role_arn = aws_iam_role.execution.arn
infrastructure_role_arn = aws_iam_role.infrastructure.arn
primary_container {
image = "nginx:latest"
}
}
terraform applyを実行すると、こちらもすぐに完了します。
aws_ecs_express_gateway_service.example: Modifying...
aws_ecs_express_gateway_service.example: Modifications complete after 5s
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Terraform側の処理は5秒で完了しましたが、実際のデプロイが完了するまでには約10分かかりました。


このデプロイ時間を変更する方法は以下ブログをご参照ください。
複数デプロイしてみる
ECS Express ModeはALBを複数サービスで共有できるらしいので、実際に試してみました。
resource "aws_ecs_express_gateway_service" "example1" {
execution_role_arn = aws_iam_role.execution.arn
infrastructure_role_arn = aws_iam_role.infrastructure.arn
primary_container {
image = "nginx:latest"
}
}
resource "aws_ecs_express_gateway_service" "example2" {
execution_role_arn = aws_iam_role.execution.arn
infrastructure_role_arn = aws_iam_role.infrastructure.arn
primary_container {
image = "nginx:latest"
}
}
resource "aws_ecs_express_gateway_service" "example3" {
execution_role_arn = aws_iam_role.execution.arn
infrastructure_role_arn = aws_iam_role.infrastructure.arn
primary_container {
image = "nginx:latest"
}
}
作成してみると、確かに1つのALBが3つのサービスで共有されていることがわかります。

その他、共有されるリソースは以下の通りでした。
共有されるリソース:
- ECSクラスター
- ALB
- ALBのセキュリティグループ
削除してみる
次に削除を試してみます。
ここで1つ注意点があるのですが、ECS Expressのリソースを先に削除する必要があります。
理由は後述します。
terraform destroy --target=aws_ecs_express_gateway_service.example{1..3}
Terraform側の削除処理は一瞬で完了します。
aws_ecs_express_gateway_service.example3: Destroying...
aws_ecs_express_gateway_service.example2: Destroying...
aws_ecs_express_gateway_service.example1: Destroying...
aws_ecs_express_gateway_service.example1: Destruction complete after 2s
aws_ecs_express_gateway_service.example2: Destruction complete after 2s
aws_ecs_express_gateway_service.example3: Destruction complete after 2s
Destroy complete! Resources: 3 destroyed.
ただし、作成時と同様に、実際のAWSリソースの削除が完了するまでには時間がかかります。
削除完了後、リソースの一覧を見ると、最終的にクラスターだけが残った状態になります。

ただし、上記のコンソール画面には表示されていませんが、以下のリソースは削除されずに残っていました。
- CloudWatch Logsのロググループ
- タスク定義
インフラストラクチャロールを一緒に消してしまうと…?
さて、ここで先ほど「ECS Expressのリソースを先に削除する必要がある」と書いた理由を説明します。
前述の通りインフラストラクチャロールは、Express ModeがALBやセキュリティグループなどのリソースを管理するために使用しています。
もしECS Expressのリソースと同時、および先にこのロールを削除してしまうと、どうなるでしょうか?
なんと、削除権限がなくなるため、リソースの削除ができなくなります。
実際に試してみたところ、以下のように削除が進まず、サービスがDRAINING状態のまま止まってしまいました。
| タイムライン | リソース一覧 |
|---|---|
![]() |
![]() |
最終的に以下の通りリソースは削除されましたが(おそらくAWS側の処理?)、削除が完了するまで1時間以上かかりました…。
そのためTerraformでterraform destroyを実行する際は、先にECS Expressのリソースを削除しましょう。

最大限にカスタマイズしてみる
一通り挙動はわかった気がするので、次は最大限カスタマイズしてみます。
最小構成はシンプルで良いのですが、実際のプロジェクトではもう少し細かい設定が必要ですよね。
「ポートを変えたい」「環境変数を渡したい」「スケーリングの設定を調整したい」など、よくある要件に対応できるか試してみました。
resource "aws_ecs_express_gateway_service" "main" {
# Required
execution_role_arn = aws_iam_role.execution.arn
infrastructure_role_arn = aws_iam_role.infrastructure.arn
# Service settings
service_name = "test-service"
cluster = aws_ecs_cluster.main.name
# Task resources
cpu = "256"
memory = "512"
# Health check
health_check_path = "/health"
# Task role
task_role_arn = aws_iam_role.task.arn
# Wait for deployment
wait_for_steady_state = true
# Primary container
primary_container {
image = "${aws_ecr_repository.app.repository_url}:latest"
container_port = 8080
aws_logs_configuration {
log_group = aws_cloudwatch_log_group.app.name
log_stream_prefix = "app"
}
environment {
name = "ENV"
value = "test"
}
environment {
name = "PORT"
value = "8080"
}
secret {
name = "DB_PASSWORD"
value_from = aws_secretsmanager_secret.db_password.arn
}
secret {
name = "API_KEY"
value_from = aws_secretsmanager_secret.api_key.arn
}
}
# Network
network_configuration {
subnets = aws_subnet.public[*].id
security_groups = [aws_security_group.ecs_tasks.id]
}
# Auto scaling
scaling_target {
min_task_count = 1
max_task_count = 2
auto_scaling_metric = "AVERAGE_CPU"
auto_scaling_target_value = 70
}
tags = {
Name = "test-app"
}
}
指定できるオプションをすべて指定したためだいぶ長くなりましたが、それでも従来のECS構成に比べればかなりコンパクトです。
デプロイ後にアクセスしてみると、以下のように環境変数やシークレットを正常に参照できていることがわかります。
{
"status": "ok",
"message": "ECS Express Mode Test App",
"environment": {
"ENV": "test",
"PORT": "8080"
},
"secrets": {
"API_KEY": "hevE...oUW6",
"DB_PASSWORD": "+qPA...}lmg"
}
}
カスタマイズポイントの解説
各設定項目について簡単に記載します。
基本設定
service_name: サービス名を明示的に指定。指定しない場合は自動生成されますが、わかりやすい名前をつけておくのがおすすめ。cluster: 使用するECSクラスター。デフォルトはdefaultクラスターが使われます。cpu/memory: タスクのリソース割り当て。デフォルトは1 vCPU / 2 GBです。health_check_path: ヘルスチェックのパス。デフォルトは/pingなので、アプリに合わせて変更しましょう。
コンテナ設定
container_port: コンテナがリッスンするポート。デフォルトは80です。command: コンテナ起動時のコマンド。DockerfileのCMDを上書きしたい場合に便利。aws_logs_configuration: CloudWatch Logsの設定。ログの出力先を指定できます。environment: 環境変数。複数指定可能です。secrets: Secrets Managerからのシークレット注入。DBのパスワードなど、秘匿情報を安全に渡せます。
ネットワーク設定
subnets: 使用するサブネット。ALBとECSの両方が指定したサブネットに配置されます。security_groups: 追加のセキュリティグループ。
スケーリング設定
min_task_count/max_task_count: タスク数の最小・最大値。auto_scaling_metric: スケーリングのメトリクス。対象としてCPUかメモリを指定できます。auto_scaling_target_value: ターゲット使用率(パーセント)。デフォルトは60%です。
その他
wait_for_steady_state: サービスが安定状態になるまでterraform applyを待機させる設定。task_role_arn: タスクからDynamoDBやS3など、他のAWSサービスを呼び出す場合に必要
どんなユースケースに適している?
最後に、ECS Express Modeがどんな場面で活用できそうか考えてみます。
向いているケース
- デモやモック: HTTPS終端、ロードバランシング、オートスケーリングが自動設定されるので、「とりあえず動くWebアプリを公開したい」というニーズにバッチリ合います
- セルフサービス基盤: AWSの深い知識がなくてもデプロイできるので、プラットフォームチームがTerraformモジュールとして開発チームに提供するのも良さそうです
向いていないケース
一方で、以下のようなケースでは従来のECSサービスの方が適していそうです。
- 非HTTPワークロード: バッチ処理やワーカーなど、ALBが不要なユースケース
- 既存ALBの流用: すでにあるALBにサービスを追加したい場合
- 細かいカスタマイズが必要: Express Modeのデフォルト設定を変更できない部分もあるため
またExpress Modeで作成されたリソースは個別に直接更新することも可能なので、
「まずはExpress Modeで始めて、必要に応じて調整を加えていく」というアプローチも取れます。
しかしその場合は、そもそもTerraformで管理する必要性は薄そうですね…。
さいごに
以上、ECS Express ModeをTerraformで触ってみた内容でした。
正直、最初は「本当にこれだけで動くの?」と半信半疑でしたが、
従来のECS構成で必要だった大量のリソースが、aws_ecs_express_gateway_serviceひとつでほぼ完結するのは衝撃的でした。
現時点ではカスタマイズできない部分も多くありますが、その辺りは今後のアップデートに期待しましょう。
とはいえ、「とりあえずコンテナをデプロイして動かしたい」という場面では、現時点でもかなり有力な選択肢になりそうです。
それでは、皆様のECS生活が円滑に進むことを願っています。













