ECS task definitions in Terraform define the blueprint that Amazon ECS uses to run container workloads. The awsecstask_definition resource captures the container image, CPU and memory allocation, port mappings, IAM execution role, logging configuration, and the operating system platform. Every ECS service references a task definition to know what to run and how. The definition is stored as a revisioned family in AWS, and Terraform creates a new revision each time the definition changes while old revisions remain retained. Services can reference the family and control whether they use the latest revision or a specific one. The process of managing these definitions with Terraform allows simplification of reusable ECS task definitions, passing dynamic variables to tasks and services, and seamless integration with other AWS services like Secrets Manager and CloudWatch.
The broader tooling context for managing Terraform includes OpenTofu as an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6. Managing Terraform at scale is supported by platforms such as Spacelift which helps manage Terraform state, build more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and many more. These capabilities affect how teams apply task definitions across environments and enforce consistency.
ECS Launch Types and Task Definition Fundamentals
With the EC2 launch type, you provision and manage the underlying EC2 instances yourself — giving you more control over instance type, pricing, and configuration. Fargate is serverless: AWS manages the compute entirely, and you only define CPU and memory at the task level. Fargate is simpler to operate; EC2 gives you more flexibility and is often more cost-effective at scale.
The difference between launch types changes the operational impact of a task definition. With EC2, the task definition interacts with an existing host environment that the user controls, which means instance selection, networking, and capacity planning remain in user control. With Fargate, the task definition is the sole source of compute sizing because AWS abstracts the host. The simplicity of Fargate reduces operational overhead but requires the user to select valid CPU and memory combinations defined by AWS for Fargate. The cost profile shifts from instance-level optimization to per-task allocation.
A task definition is a blueprint for your container workload. It defines the container image, CPU and memory allocation, port mappings, IAM execution role, logging configuration, and the operating system platform. In Terraform, it is managed using the awsecstask_definition resource. Every ECS service references a task definition to know what to run and how. The resource becomes the contract between infrastructure code and runtime behavior. Changing the task definition does not automatically update running services unless the service is configured to adopt the new revision.
Do I need a load balancer to deploy ECS with Terraform? No, a load balancer is optional for getting an ECS service running. The absence of a load balancer simplifies initial deployment and testing. The task definition can expose ports directly without external routing. When a load balancer is later added, the service definition integrates with ALBs while the task definition remains the source of container configuration.
awsecstask_definition Resource Structure
The core resource is awsecstaskdefinition. For Fargate, the resource requires requirescompatibilities = ["FARGATE"] and networkmode = "awsvpc". Task-level resources are set via cpu and memory attributes. IAM roles are bound via executionrolearn and taskrole_arn. Container definitions are provided as a JSON-encoded list.
A basic task definition example shows these relationships:
```
resource "awsecstaskdefinition" "app" {
family = "myapp"
requirescompatibilities = ["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
executionrolearn = awsiamrole.ecsexecution.arn
taskrolearn = awsiamrole.ecstask.arn
Container definitions
containerdefinitions = jsonencode([
{
name = "app"
image = "${var.ecrrepositoryurl}:${var.imagetag}"
essential = true
portMappings = [
{
containerPort = 8080
hostPort = 8080
protocol = "tcp"
}
]
Logging
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = awscloudwatchloggroup.app.name
"awslogs-region" = var.awsregion
"awslogs-stream-prefix" = "app"
}
}
Environment variables
environment = [
{ name = "PORT", value = "8080" },
{ name = "NODEENV", value = var.environment },
{ name = "LOGLEVEL", value = "info" }
]
Secrets from SSM Parameter Store or Secrets Manager
secrets = [
{
name = "DATABASEURL"
valueFrom = awsssmparameter.dburl.arn
},
{
name = "APIKEY"
valueFrom = "${awssecretsmanagersecret.apikey.arn}:api_key::"
}
]
Health check
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval = 30
timeout = 5
retries =
```
The structure couples task-level sizing with container-level configuration. The cpu field accepts values such as 256, 512, 1024, 2048, 4096. The memory field is expressed in MiB, with 1024 representing 1 GB. The container definitions block contains the name, image, essential flag, portMappings, logConfiguration, environment, secrets, and healthCheck.
A parameterized example demonstrates reuse:
resource "aws_ecs_task_definition" "app" {
family = var.task_family
container_definitions = jsonencode([
{
name = "${var.container_name}"
image = "${var.container_image}"
cpu = var.cpu
memory = var.memory
essential = true
environment = var.environment_variables
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = var.log_group
"awslogs-region" = var.region
"awslogs-stream-prefix" = var.log_stream_prefix
}
}
}
])
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
execution_role_arn = aws_iam_role.ecs_task_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
cpu = var.task_cpu
memory = var.task_memory
}
Variables that enable reuse include:
- task_family
- container_name
- container_image
- cpu
- memory
- environment_variables
The use of variables decouples the shape of the definition from specific values. This supports multiple services sharing the same definition pattern with different images, CPU, memory, and logging targets.
Parameterized and Reusable Task Definitions
By utilizing Terraform, you can:
- Simplify the creation of reusable ECS task definitions.
- Pass dynamic variables to tasks and services.
- Seamlessly integrate with other AWS services like Secrets Manager and CloudWatch.
ECS Task Definitions:
- Define container settings (CPU, memory, image, etc.).
- Use variables to make definitions reusable across multiple services.
ECS Services:
- Manage the number of tasks, scaling policies, and integration with ALBs.
AWS Services Integration:
- Connect to Secrets Manager for sensitive data.
- Enable CloudWatch logging and metrics.
Deploying ECS services using Terraform allows you to maintain a modular, reusable, and parameter-driven infrastructure. In this guide, we will focus on creating and reusing ECS task definitions, configuring services, and integrating other AWS components effectively with Terraform.
Creating a robust ECS deployment often involves repetitive configurations and complex setups. Reuse reduces drift and duplication across environments. Parameter-driven definitions allow the same module to produce task definitions for development, staging, and production with different images, resource sizes, and logging groups.
Fargate Specific Configuration and Resource Sizing
This guide covers creating task definitions in Terraform for Fargate, including single and multi-container tasks, secrets management, health checks, and resource sizing.
For Fargate, use network_mode = "awsvpc" and choose from the valid CPU/memory combinations. The task-level cpu and memory fields determine the allocation for the task as a whole. Container-level cpu and memory can be specified within container definitions to further partition resources inside the task.
The example with cpu = 512 and memory = 1024 shows a 0.5 vCPU and 1 GB allocation. Another example uses cpu = 256 and memory = 512. These values must match AWS allowed combinations for Fargate. Selecting appropriate sizing impacts cost and performance. Over-provisioning increases cost without benefit. Under-provisioning risks throttling and unhealthy tasks.
Resource sizing decisions are made at the task definition level and propagate to the service. Services reference the task definition family, and you control whether they use the latest revision or a specific one.
Container Definitions Deep Dive
Container definitions are the inner JSON objects within container_definitions. They specify name, image, cpu, memory, essential, environment, logConfiguration, portMappings, secrets, healthCheck, mountPoints, volumesFrom.
A complete container definition example:
[{
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 = []
}]
Essential = true means the container must start successfully for the task to be considered healthy. Port mappings define containerPort and hostPort with protocol tcp. For Fargate with awsvpc network mode, hostPort must match containerPort.
The logConfiguration uses logDriver = "awslogs" with options for awslogs-group, awslogs-region, and awslogs-stream-prefix. This directs container logs to CloudWatch Logs. Centralized logging enables monitoring and troubleshooting across tasks.
Environment variables are supplied via the environment list with name and value pairs. Secrets are supplied via the secrets list with name and valueFrom pointing to SSM Parameter Store or Secrets Manager ARNs. Use SSM Parameter Store or Secrets Manager for sensitive values - never put credentials in environment variables directly.
Secrets integration prevents secret leakage in task definition state. The valueFrom field references awsssmparameter.dburl.arn or "${awssecretsmanagersecret.apikey.arn}:api_key::". This ensures secrets are injected at runtime without persisting plaintext values in Terraform state.
Health Checks and Multi-Container Tasks
Health checks are defined inside the container definition with command, interval, timeout, retries. The example uses:
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval = 30
timeout = 5
retries =
Health checks allow ECS to determine container health independently of load balancer health checks. Frequent checks with short timeouts detect failures quickly. The impact is faster replacement of unhealthy containers and improved service stability.
Multi-container tasks let you run sidecars for monitoring, logging, or proxying alongside your application. The task definition contains multiple entries in the container_definitions array. Each container shares the task’s network namespace and resources. Sidecars can be non-essential and handle ancillary responsibilities without affecting the primary application container.
Versioning, Outputs, and Service Integration
Each time you change a task definition, Terraform creates a new revision. The old revisions are kept:
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
}
Outputs expose taskdefinitionfamily, taskdefinitionarn, containername, containerport.
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
}
Versioning enables safe rollouts. Services reference the task definition family, and you control whether they use the latest revision or a specific one. Pinning to a revision provides stability. Allowing latest revision enables continuous deployment.
Service integration example:
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"
}
The service references the task definition ARN, desiredcount, cluster, network configuration, and launchtype. Changes to the task definition create a new revision; the service can be updated to use the new revision.
Module Usage and Dynamic Generation
A Terraform module for creating Amazon ECS Task Definitions exists 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 purpose of this module is to generate a valid Amazon ECS Task Definition dynamically.
- Have Terraform generate valid task definitions dynamically
- Update the ECS task definition and trigger new service deployments automatically (see examples/ecsupdateservice.tf)
This module uses the same parameters as the ContainerDefinition object.
The module is not compatible with versions of Terraform less than v0.12.x. Please refer to the official documentation for upgrading to the latest version of Terraform.
Dynamic generation reduces manual JSON construction and enforces consistent parameter naming. Modules encapsulate complexity and expose inputs for family, container definitions, IAM roles, and network mode. This aligns with the practice of simplifying the creation of reusable ECS task definitions.
Migration Patterns and Consul Mesh Integration
Register existing ECS tasks with Terraform.
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 ecstaskdefinition resource so that they can then be converted to use the mesh-task module.
The migration requires existing task definitions to be present in Terraform. The awsecstask_definition resource is replaced with the mesh-task module so that Consul adds the necessary dataplane containers that enable your task to join the mesh.
The migration pattern shows that task definitions are the entry point for extending ECS workloads with service mesh capabilities. Keeping the task definition in Terraform ensures that the mesh sidecar containers are provisioned consistently with the application container.
Conclusion
ECS task definitions in Terraform specify everything about how your 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. Use SSM Parameter Store or Secrets Manager for sensitive values - never put credentials in environment variables directly. Multi-container tasks let you run sidecars for monitoring, logging, or proxying alongside your application.
The task definition resource acts as the central contract between infrastructure code and container runtime. Parameterization makes definitions reusable across services and environments. Versioning provides safety for rollbacks and controlled updates. Integration with Secrets Manager, CloudWatch, and IAM roles closes the security and observability loop. Launch type selection between EC2 and Fargate shapes control versus operational simplicity. Modules and migration patterns extend the definition to support dynamic generation and service mesh adoption. Managing these definitions with Terraform, OpenTofu, and tooling such as Spacelift enables scale, policy enforcement, and drift detection across large ECS fleets.