An Amazon Elastic Container Service task definition in Terraform is the declarative blueprint that describes precisely how a container workload is instantiated, scheduled, and executed within an ECS cluster. The resource aws_ecs_task_definition is the central Terraform construct that encodes the image, CPU and memory allocation, port mappings, IAM execution role, logging configuration, and operating system platform for every task that runs. Every ECS service references a task definition to know what to run and how, which means the task definition is the immutable specification that binds infrastructure intent to runtime behavior. In practice the task definition acts as a versioned contract between the developer who publishes a container image and the operations layer that provisions compute. When Terraform manages the definition, changes are applied as new revisions rather than in-place mutations, which creates an auditable history of workload specifications and enables safe rollbacks.
The task definition is not isolated from the launch type decision. 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. This distinction directly impacts the fields that must be set in the task definition. Fargate requires network_mode = "awsvpc" and requires_compatibilities = ["FARGATE"]. EC2 launch types allow more control over the host and typically require explicit operating system family and CPU architecture attributes such as operating_system_family = "LINUX" and cpu_architecture = "X86_64" when Amazon Linux AMI based instances are used.
OpenTofu is 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. The existence of OpenTofu is relevant because task definitions created with Terraform today may be managed with either engine, and the aws_ecs_task_definition resource syntax is compatible across both. Spacelift 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 are particularly valuable for task definitions because they are frequently revised, and drift detection can identify manual changes to container definitions or IAM role attachments that would otherwise diverge from the declared state.
ECS Task Definition Fundamentals in Terraform
The 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 aws_ecs_task_definition resource. Every ECS service references a task definition to know what to run and how.
The impact of treating the task definition as a blueprint is that all runtime parameters become codified. Image tags, resource requests, environment variables, secrets, health check commands, and logging destinations are all expressed as code rather than manual console edits. This codification enables peer review, version control, and repeatable deployments across environments. The contextual layer connects the task definition to the service, the cluster, and the capacity provider. A service points to a family name and optionally a specific revision. The cluster provides the compute, and the capacity provider binds an Auto Scaling Group to the cluster when using EC2 launch type. Changes to the task definition automatically create a new revision while preserving old revisions, which allows services to be updated to the latest revision or pinned to a known good revision.
Launch Type Distinction EC2 vs Fargate
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 practical consequence for Terraform authors is that Fargate task definitions must specify network_mode = "awsvpc" and a compatible CPU and memory pairing. EC2 task definitions can use network_mode = "bridge" or awsvpc and typically require explicit resource requirements for the container and the task. The choice influences cost modeling, scaling behavior, and networking design. Fargate tasks attach an elastic network interface per task, which simplifies networking but removes the ability to share a host network. EC2 launch type allows host-level optimizations and custom instance types, which can lower cost at scale but increases operational burden.
A comparison of launch type implications can be expressed as:
| Attribute | EC2 Launch Type | Fargate Launch Type |
|---|---|---|
| Compute management | User managed EC2 instances | AWS managed |
| Network mode | bridge or awsvpc | awsvpc required |
| Resource definition | Per container and task | Per task only |
| Operating system control | Full control via instance AMI | Limited to supported platforms |
| Cost profile | Often more cost-effective at scale | Simpler pricing per task |
Core Resource awsecstask_definition
Manages an Ecs Task Definition resource. A minimal configuration to get started. Refer to the Terraform Registry docs for all available arguments.
```
resource "awsecstask_definition" "example" {
Required arguments
name = "my-ecs-task-definition"
}
```
The minimal configuration demonstrates that a task definition can be created with a name alone, though real workloads require additional arguments such as family, requirescompatibilities, networkmode, cpu, memory, executionrolearn, taskrolearn, and container_definitions. The impact of a minimal definition is that Terraform will accept it but AWS will reject it at deployment time if required fields are missing for the chosen launch type. The contextual layer is that the resource supports both creation and subsequent updates through new revisions. The name argument is often used for identification, while the family argument is used for service referencing.
Minimal Configuration and Data Source
```
data "awsecstask_definition" "example" {
Required arguments
Refer to the Terraform Registry docs for details
}
```
Provides details about a specific Ecs Task Definition. A minimal configuration to get started. Refer to the Terraform Registry docs for all available arguments.
The data source is useful when you need to read an existing task definition revision, for example to reference its ARN or revision number in outputs or downstream resources. Using a data source avoids hard-coding ARNs and allows Terraform to discover the latest revision of a family. The impact is safer dependency management in modules where the task definition may be created in a separate stack. The contextual layer connects to outputs such as task_definition_arn and task_definition_revision that are commonly exported for service modules.
Fargate Task Definition Example with Container Definitions
This guide covers creating task definitions in Terraform for Fargate, including single and multi-container tasks, secrets management, health checks, and resource sizing.
Basic Task Definition
```
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 example encodes the task-level resources cpu and memory. CPU 512 corresponds to 0.5 vCPU and valid values include 256, 512, 1024, 2048, 4096. Memory 1024 corresponds to 1 GB. The execution role ARN enables the ECS agent to pull images and write logs. The task role ARN grants permissions to the running containers. Container definitions are JSON encoded because Terraform requires a string for the container_definitions argument.
Container Definition Attributes
Container definitions specify everything about how your containers run: the image, CPU and memory allocation, port mappings, logging, environment variables, secrets, health checks, and volumes.
A typical container definition includes:
- name
- image
- essential
- portMappings
- logConfiguration
- environment
- secrets
- healthCheck
The essential flag determines whether the task stops if the container fails. Port mappings define how container ports map to host ports. For Fargate with awsvpc, hostPort and containerPort are typically the same.
The impact of detailed container definitions is that all runtime behavior is declared. Changes to environment variables or secrets create a new revision, which allows safe promotion through environments.
Logging Configuration and Environment Variables
Logging is configured via logConfiguration with logDriver = "awslogs" and options for awslogs-group, awslogs-region, and awslogs-stream-prefix. This directs container stdout and stderr to CloudWatch Logs.
Environment variables are passed as a list of objects with name and value. The example sets PORT, NODEENV, and LOGLEVEL. The impact is that configuration is visible in code, which aids debugging but also requires discipline to avoid leaking secrets.
The contextual layer is that logging and environment variables together form the observability surface of the task. CloudWatch Logs integration enables log aggregation and metric filtering, while environment variables control application behavior without rebuilding the image.
Secrets Management with SSM and Secrets Manager
Secrets from SSM Parameter Store or Secrets Manager are referenced via the secrets block with name and valueFrom.
secrets = [
{
name = "DATABASE_URL"
valueFrom = aws_ssm_parameter.db_url.arn
},
{
name = "API_KEY"
valueFrom = "${aws_secretsmanager_secret.api_key.arn}:api_key::"
}
]
Use SSM Parameter Store or Secrets Manager for sensitive values - never put credentials in environment variables directly. The impact is reduced risk of secret leakage in state files and console history. The contextual layer is that the ECS task execution role must have permissions to decrypt the secret, which ties IAM policy design to the task definition.
Health Checks and Port Mappings
Health checks are defined inside the container definition.
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 and take corrective action. The interval of 30 seconds with timeout of 5 seconds means ECS will attempt a health check every 30 seconds and wait up to 5 seconds for a response. The impact is faster detection of unhealthy containers and automatic replacement by the service scheduler.
Port mappings define containerPort 8080 and hostPort 8080 with protocol tcp. The impact is that the container is reachable on that port within the awsvpc network. The contextual layer connects to service discovery and load balancing.
Versioning Revisions and Outputs
Each time you change a task definition, Terraform creates a new revision. The old revisions are kept.
Outputs are commonly used to expose task definition metadata.
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
}
Services reference the task definition family, and you control whether they use the latest revision or a specific one.
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
}
The impact of versioning is safe rollbacks. If a new revision introduces an error, the service can be reverted to the previous revision ARN. The contextual layer is that outputs enable module composition, allowing a task definition module to publish its ARN and revision for a service module to consume.
Capacity Providers and Cluster Configuration Context
Whereas “awsecsclustercapacityproviders” binds the ASG capacity provider with the ECS cluster created in Step 1.
resource "aws_ecs_capacity_provider" "ecs_capacity_provider" {
name = "test1"
auto_scaling_group_provider {
auto_scaling_group_arn = aws_autoscaling_group.ecs_asg.arn
managed_scaling {
maximum_scaling_step_size = 1000
minimum_scaling_step_size = 1
status = "ENABLED"
target_capacity = 3
}
}
}
resource "aws_ecs_cluster_capacity_providers" "example" {
cluster_name = aws_ecs_cluster.ecs_cluster.name
capacity_providers = [aws_ecs_capacity_provider.ecs_capacity_provider.name]
default_capacity_provider_strategy {
base = 1
weight = 100
capacity_provider = aws_ecs_capacity_provider.ecs_capacity_provider.name
}
}
The capacity provider links an Auto Scaling Group to an ECS cluster, enabling EC2 launch type tasks to scale based on cluster demand. The impact is automated scaling of the underlying compute without manual intervention. The contextual layer connects to the task definition because EC2 launch type task definitions must be compatible with the instance types in the ASG.
Load Balancer Considerations for ECS Services
Do I need a load balancer to deploy ECS with Terraform?
No, a load balancer is optional for getting an ECS service running. However, without one, you would need to access containers directly via EC2 instance IPs, which are not stable across deployments. For any real workload, an Application Load Balancer is strongly recommended as it handles traffic distribution, health checks, and allows zero-downtime deployments.
The impact is that omitting a load balancer simplifies initial testing but introduces operational fragility in production. The contextual layer is that the task definition’s port mappings and health checks interact with the load balancer’s health checks, creating a layered health model.
Runtime Platform and Network Mode
Step 4: Create ECS task definition with Terraform
As described in the ECS overview section above, we now define the container task template to run on the ECS cluster using the image we pushed in Step 1.
Some of the important points to note here are:
- We have defined the network mode to be “awsvpc”. This tells the ECS cluster to use the VPC networking we have defined in the “VPC setup” section.
- We have provided the task definition with the ecsTaskExecutionRole.
- Defined CPU resource requirement as 256.
- The runtime platform is an important attribute. Since we are using Amazon Linux AMI for our EC2 instances, the operatingsystemfamily is specified as “LINUX,” and the CPU architecture is set as “X86_64”
The runtime platform attributes ensure the task definition is only scheduled on compatible hosts. The impact is prevention of scheduling failures due to OS or architecture mismatch. The contextual layer ties VPC setup to networking, IAM roles to execution permissions, and CPU requirements to instance sizing.
OpenTofu and Terraform State Management Ecosystem
OpenTofu is 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.
Discover better way to manage Terraform at scale
Spacelift helps manage Terraform state, build more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and many more.
The impact of OpenTofu is continuity of the Terraform workflow with an open-source license. The impact of Spacelift is operational governance for teams managing many task definitions across environments. Drift detection is especially valuable because manual edits to task definitions in the console can create divergence from the Terraform state.
Conclusion
AWS ECS task definitions managed with Terraform provide a complete declarative model for container workloads. The aws_ecs_task_definition resource captures image, CPU, memory, network mode, IAM roles, logging, environment variables, secrets, health checks, and runtime platform. Fargate task definitions require awsvpc network mode and compatible CPU and memory pairings, while EC2 launch type task definitions benefit from explicit operating system family and CPU architecture settings and can be coupled to capacity providers that bind Auto Scaling Groups to clusters. Versioning creates new revisions on every change, preserving history and enabling safe rollbacks, while outputs expose ARNs and revision numbers for service composition. Secrets should be sourced from SSM Parameter Store or Secrets Manager rather than embedded environment variables. Health checks and port mappings define observability and connectivity. Load balancers remain optional for initial deployment but are strongly recommended for production traffic distribution and zero-downtime deployments. The ecosystem around Terraform, including OpenTofu and state management platforms like Spacelift, reinforces governance and reliability for task definition lifecycles at scale.