Terraform AWS ECS Task Definition Deep Dive for Fargate and Multi-Container Workloads

Creating a reliable, reproducible Amazon ECS task definition with Terraform is foundational to running containers on AWS Fargate. The aws_ecs_task_definition resource encodes the entire launch contract for a task: the compute sizing, IAM permissions, container image, networking, logging, environment, secrets, health checks, and volumes. When the definition changes, Terraform produces a new revision that can be referenced by services for controlled rollouts.

Introduction

This article covers the Terraform resource and data source for ECS task definitions, Fargate-specific configuration patterns, secrets management, health checks, versioning, and module-based generation. The focus is on concrete configuration derived from reference implementations for Fargate workloads, including single-container and multi-container task patterns.

Core Resource Definition

The Terraform AWS provider exposes aws_ecs_task_definition to manage an ECS Task Definition resource.

resource "aws_ecs_task_definition" "example" { # Required arguments name = "my-ecs-task-definition" }

A minimal configuration can start with a name. A production Fargate definition expands to include compatibility, network mode, CPU and memory sizing, IAM roles, and container definitions.

Fargate Task Definition Skeleton

A basic Fargate task definition includes the family, compatibility, network mode, task-level resources, IAM roles, and container definitions.

resource "aws_ecs_task_definition" "app" { family = "myapp" requires_compatibilities = ["FARGATE"] network_mode = "awsvpc" # Required for Fargate # Task-level resources cpu = 512 # 0.5 vCPU (256, 512, 1024, 2048, 4096) memory = 1024 # 1 GB # IAM roles execution_role_arn = aws_iam_role.ecs_execution.arn task_role_arn = aws_iam_role.ecs_task.arn # Container definitions container_definitions = jsonencode([ { name = "app" image = "${var.ecr_repository_url}:${var.image_tag}" essential = true portMappings = [ { containerPort = 8080 hostPort = 8080 protocol = "tcp" } ]

Container Definitions and Task-Level Attributes

Container definitions are encoded as JSON and passed via container_definitions. The task-level attributes control the whole task.

Task-level attributes from reference examples:

Attribute Example Value Purpose
family myapp Logical grouping for revisions
requires_compatibilities ["FARGATE"] Launch type constraint
network_mode awsvpc Required for Fargate
cpu 512 0.5 vCPU options: 256, 512, 1024, 2048, 4096
memory 1024 Memory in MB, e.g. 1 GB
executionrolearn awsiamrole.ecs_execution.arn Pull image and write logs
taskrolearn awsiamrole.ecs_task.arn Container runtime permissions
container_definitions jsonencode([...]) List of container specs

Fargate requires network_mode = "awsvpc" and valid CPU/memory combinations. The example uses cpu = 512 and memory = 1024.

Container Configuration Details

Inside container_definitions, each container can specify image, essential flag, port mappings, logging, environment, secrets, and health checks.

Image and Essentials

name = "app" image = "${var.ecr_repository_url}:${var.image_tag}" essential = true

Essential containers determine task health; if an essential container stops, the task stops.

Port Mappings

portMappings = [ { containerPort = 8080 hostPort = 8080 protocol = "tcp" } ]

With awsvpc network mode, hostPort maps to the ENI. For the reference example example-client-app, port mapping uses containerPort = 9090 and hostPort = 9090 with protocol = "tcp".

Logging Configuration

logConfiguration = { logDriver = "awslogs" options = { "awslogs-group" = aws_cloudwatch_log_group.app.name "awslogs-region" = var.aws_region "awslogs-stream-prefix" = "app" } }

Logging uses the awslogs driver with options for log group, region, and stream prefix.

Environment Variables

environment = [ { name = "PORT", value = "8080" }, { name = "NODE_ENV", value = var.environment }, { name = "LOG_LEVEL", value = "info" } ]

Environment variables are suitable for non-sensitive configuration. Secrets should not be placed directly in environment variables.

Secrets from SSM and Secrets Manager

secrets = [ { name = "DATABASE_URL" valueFrom = aws_ssm_parameter.db_url.arn }, { name = "API_KEY" valueFrom = "${aws_secretsmanager_secret.api_key.arn}:api_key::" } ]

Secrets are injected from SSM Parameter Store or Secrets Manager via valueFrom. The reference guide emphasizes using SSM Parameter Store or Secrets Manager for sensitive values and never putting credentials in environment variables directly.

Health Checks

healthCheck = { command = ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"] interval = 30 timeout = 5 retries = }

Health checks define the command, interval, timeout, and retries for container health monitoring.

Task Definition Versioning and Outputs

Each time a task definition changes, Terraform creates a new revision. Old revisions are kept.

Outputs for tracking:

output "task_definition_arn" { description = "Full ARN including revision number" value = aws_ecs_task_definition.app.arn } output "task_definition_revision" { description = "Current revision number" value = aws_ecs_task_definition.app.revision }

Additional outputs commonly used:

output "task_definition_family" { description = "Task definition family name" value = aws_ecs_task_definition.app.family } output "task_definition_arn" { description = "Task definition ARN" value = aws_ecs_task_definition.app.arn } output "container_name" { description = "Main container name" value = "app" } output "container_port" { description = "Main container port" value = 8080 }

Services reference the task definition family, and you control whether they use the latest revision or a specific one.

Data Source for Existing Definitions

The provider also offers a data source to read an existing definition.

data "aws_ecs_task_definition" "example" { # Required arguments # Refer to the Terraform Registry docs for details }

The data source provides details about a specific ECS Task Definition.

Multi-Container Tasks and Sidecars

ECS task definitions in Terraform specify everything about how containers run: the image, CPU and memory allocation, port mappings, logging, environment variables, secrets, health checks, and volumes.

For Fargate, use network_mode = "awsvpc" and choose from the valid CPU/memory combinations. Multi-container tasks let you run sidecars for monitoring, logging, or proxying alongside your application.

The guide covers creating task definitions in Terraform for Fargate, including single and multi-container tasks, secrets management, health checks, and resource sizing.

Example Migration to Consul Mesh

Registering existing ECS tasks with Terraform for Consul integration requires the task to be defined with aws_ecs_task_definition before conversion.

Example existing task definition:

resource "aws_ecs_task_definition" "my_task" { family = "my_task" requires_compatibilities = ["FARGATE"] network_mode = "awsvpc" cpu = 256 memory = 512 execution_role_arn = "arn:aws:iam::111111111111:role/execution-role" task_role_arn = "arn:aws:iam::111111111111:role/task-role" container_definitions = jsonencode( [{ name = "example-client-app" image = "docker.io/org/my_task:v0.0.1" essential = true portMappings = [ { containerPort = 9090 hostPort = 9090 protocol = "tcp" } ] cpu = 0 mountPoints = [] volumesFrom = [] }] ) }

Service linkage:

resource "aws_ecs_service" "my_task" { name = "my_task" cluster = "arn:aws:ecs:us-east-1:111111111111:cluster/my-cluster" task_definition = aws_ecs_task_definition.my_task.arn desired_count = 1 network_configuration { subnets = ["subnet-abc123"] } launch_type = "FARGATE" }

To migrate existing tasks to Consul, rewrite the existing Terraform code for your tasks so that the container definitions include the mesh-task Terraform module. Your tasks must already be defined in Terraform using the ecs_task_definition resource so that they can then be converted to use the mesh-task module. Replace the aws_ecs_task_definition resource with the mesh-task module so that Consul adds the necessary dataplane containers that enable your task to join the mesh.

Module-Based Task Definition Generation

A Terraform module for creating Amazon ECS Task Definitions can generate a valid Amazon ECS Task Definition dynamically. The module is not compatible with versions of Terraform less than v0.12.x.

Example usage:

provider "aws" {} module "mongo-task-definition" { source = "github.com/mongodb/terraform-aws-ecs-task-definition" family = "mongo" image = "mongo:3.6" memory = 512 name = "mongo" portMappings = [ { containerPort = 27017 }, ] }

Invoking Terraform creates an ECS task definition with the following containerDefinitions:

[ { "command": null, "cpu": null, "disableNetworking": false, "dnsSearchDomains": null, "dnsServers": null, "dockerLabels": null, "dockerSecurityOptions": null, "entryPoint": null, "environment": null, "essential": true, "extraHosts": null, "healthCheck": null, "hostname": null, "image": "mongo:3.6", "interactive": false, "links": null, "linuxParameters": null, "logConfiguration": null, "memory": 512, "memoryReservation": null, "mountPoints": null, "name": "mongo", "portMappings": [{"containerPort":27017}], "privileged": false, "pseudoTerminal": false, "readonlyRootFilesystem": false, "repositoryCredentials": null, "resourceRequirements": null, "secrets": null, "systemControls": null, "ulimits": null, "user": null, "volumesFrom": null, "workingDirectory": null } ]

By default, this module creates a task definition with a single container definition. The purpose of the module is to generate a valid Amazon ECS Task Definition dynamically. A task definition is required to run Docker containers in Amazon ECS. A task definition contains a list of container definitions received by the Docker daemon to create a container instance.

The module uses the same parameters as the ContainerDefinition object. Have Terraform generate valid task definitions dynamically and update the ECS task definition and trigger new service deployments automatically.

Practical Configuration Table

Common Fargate container definition fields:

Field Example Notes
name app Container name
image docker.io/org/my_task:v0.0.1 Container image
essential true Task stops if container stops
cpu 0 Container-level CPU override
portMappings containerPort 8080 Port mapping spec
logConfiguration awslogs CloudWatch logging
environment PORT=8080 Non-sensitive vars
secrets DATABASE_URL SSM/Secrets Manager
healthCheck CMD-SHELL curl... Health monitoring

Conclusion

Terraform AWS ECS task definitions provide a complete declarative model for container workloads on Fargate. The aws_ecs_task_definition resource captures task-level sizing with cpu and memory, IAM roles for execution and task, and a JSON-encoded list of container definitions. Fargate mandates network_mode = "awsvpc" and valid CPU/memory pairings. Secrets must be sourced from SSM Parameter Store or Secrets Manager via secrets, not hard-coded in environment. Health checks, logging with awslogs, and port mappings are defined per container. Versioning is automatic: changes produce new revisions while prior revisions remain available for rollback. Outputs expose ARN, revision, family, container name, and port for service wiring. Module-based generation offers an alternative path for dynamic definition creation, with default single-container output. Migrating existing definitions, such as for Consul mesh integration, requires the task to already exist as an aws_ecs_task_definition resource so the container definitions can be rewritten to include the mesh-task module.

Sources

  1. https://oneuptime.com/blog/post/2026-02-23-create-ecs-task-definitions-in-terraform/view
  2. https://awsfundamentals.com/terraform/ecs/ecs-task-definition
  3. https://awsfundamentals.com/terraform/ecs/ecs-task-definition-data
  4. https://developer.hashicorp.com/consul/docs/register/service/ecs/migrate
  5. https://github.com/TerraformFoundation/terraform-aws-ecs-task-definition

Related Posts