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
- https://oneuptime.com/blog/post/2026-02-23-create-ecs-task-definitions-in-terraform/view
- https://awsfundamentals.com/terraform/ecs/ecs-task-definition
- https://awsfundamentals.com/terraform/ecs/ecs-task-definition-data
- https://developer.hashicorp.com/consul/docs/register/service/ecs/migrate
- https://github.com/TerraformFoundation/terraform-aws-ecs-task-definition