Terraform AWS ECS es la combinación ideal para desplegar contenedores en producción de forma declarativa, reproducible y sin gestionar servidores. En esta guía crearás un clúster de Amazon ECS con el motor Fargate desde cero usando infraestructura como código, definiendo la red, los roles, la definición de tarea y el servicio, todo versionado en Git y listo para escalar. Al terminar tendrás una API en contenedor corriendo tras un balanceador, aprovisionada con un solo terraform apply.
En este artículo aprenderás a:
- Estructurar un proyecto de Terraform AWS ECS con Fargate paso a paso.
- Definir clúster, task definition, servicio y balanceador con IaC.
- Configurar los roles IAM y los grupos de seguridad necesarios.
- Aplicar buenas prácticas de estado remoto, variables y escalado.
¿Qué es Terraform AWS ECS y por qué usar Fargate?
Amazon ECS (Elastic Container Service) es el orquestador de contenedores gestionado de AWS. Con Terraform AWS ECS describes todo ese orquestador como código: en lugar de hacer clic en la consola, defines en ficheros .tf qué imagen ejecutar, cuánta CPU y memoria asignar y cómo exponer el servicio. El motor Fargate añade la pieza clave: ejecuta tus contenedores sin que tengas que aprovisionar ni parchear instancias EC2, pagando solo por los recursos que consume cada tarea.
Frente a desplegar a mano, gestionar ECS con Terraform aporta ventajas decisivas para cualquier equipo DevOps:
- Reproducibilidad: el mismo código genera entornos idénticos de desarrollo, staging y producción.
- Revisión y auditoría: cada cambio pasa por un pull request y queda en el historial de Git.
- Escalado controlado: ajustar réplicas o recursos es cambiar una variable, no navegar por la consola.
- Integración: encaja con tu red definida con Terraform AWS VPC y tus imágenes almacenadas con Terraform AWS ECR.
Arquitectura de una solución Terraform AWS ECS
Antes de escribir código conviene entender las piezas que orquesta Terraform AWS ECS y cómo se relacionan. Una arquitectura típica sobre Fargate se compone de varios elementos que crearemos como recursos independientes pero interconectados.
- Clúster ECS: la agrupación lógica donde se ejecutan tus servicios y tareas.
- Task definition: la plantilla que describe el contenedor: imagen, CPU, memoria, puertos y variables de entorno.
- Servicio ECS: el controlador que mantiene el número de tareas deseado y las reemplaza si fallan.
- Fargate: el motor de cómputo serverless que ejecuta cada tarea sin instancias que administrar.
- Application Load Balancer: distribuye el tráfico entrante entre las réplicas sanas.
- Roles IAM y grupos de seguridad: definen permisos y aislamiento de red.
El flujo es sencillo: el usuario accede al DNS del balanceador, este reenvía la petición a una tarea sana registrada en el target group, y la tarea —lanzada por el servicio sobre Fargate— responde. Todo ese diagrama queda descrito de forma declarativa, de modo que Terraform AWS ECS calcula automáticamente el orden de creación y las dependencias entre recursos. Esta visión de conjunto te ayudará a entender cada fichero que escribiremos a continuación.
Requisitos previos para Terraform AWS ECS
Antes de escribir el primer recurso de Terraform AWS ECS, prepara tu entorno. El despliegue es ligero, pero necesitas los permisos y herramientas adecuados.
- Terraform 1.6 o superior (o OpenTofu) instalado en tu máquina.
- Una cuenta de AWS con credenciales configuradas (
aws configureo variables de entorno). - Permisos IAM para crear recursos de ECS, IAM, EC2 (red) y ELB.
- Una imagen de contenedor publicada en un registro (ECR o Docker Hub).
- Una VPC con al menos dos subredes en zonas de disponibilidad distintas.
Si aún no controlas los permisos, te será útil repasar cómo definir roles y políticas con Terraform AWS IAM, porque ECS necesita roles específicos que crearemos más adelante.
Estructura del proyecto Terraform AWS ECS
Organiza el proyecto en ficheros separados por responsabilidad. Esta estructura mantiene el código legible y facilita el mantenimiento a medida que crece la infraestructura.
terraform-ecs/
├── provider.tf # Proveedor AWS y versión
├── variables.tf # Variables parametrizables
├── network.tf # Security groups
├── iam.tf # Roles de ejecución de la tarea
├── ecs.tf # Clúster, task definition y servicio
├── alb.tf # Balanceador y target group
└── outputs.tf # Salidas útiles (DNS del ALB)
Empezamos por el proveedor. Este bloque fija la versión del proveedor de AWS para garantizar despliegues reproducibles y define la región donde se creará el clúster.
# provider.tf
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.60"
}
}
}
provider "aws" {
region = var.aws_region
}
Con el proveedor definido, declaramos las variables que parametrizan el despliegue. Externalizar estos valores te permite reutilizar el mismo código en varios entornos cambiando solo un fichero .tfvars.
# variables.tf
variable "aws_region" { default = "eu-west-1" }
variable "app_name" { default = "demo-api" }
variable "container_image" { default = "nginxdemos/hello:latest" }
variable "container_port" { default = 80 }
variable "vpc_id" { type = string }
variable "subnet_ids" { type = list(string) }
variable "desired_count" { default = 2 }
Ya tenemos la base. En los siguientes apartados construiremos los roles, el clúster y el servicio que completan el despliegue de Terraform AWS ECS.
Roles IAM y red para el clúster Terraform AWS ECS
Fargate necesita un rol de ejecución de tarea para que ECS pueda descargar la imagen y escribir logs en CloudWatch. Lo definimos en iam.tf junto con la política gestionada oficial de AWS.
# iam.tf
data "aws_iam_policy_document" "assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "task_execution" {
name = "${var.app_name}-exec"
assume_role_policy = data.aws_iam_policy_document.assume.json
}
resource "aws_iam_role_policy_attachment" "exec" {
role = aws_iam_role.task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
El rol anterior aplica el principio de mínimo privilegio: solo permite las acciones imprescindibles para ejecutar la tarea. Ahora definimos los grupos de seguridad en network.tf: uno para el balanceador (abierto al puerto 80) y otro para las tareas (solo accesible desde el balanceador).
# network.tf
resource "aws_security_group" "alb" {
name = "${var.app_name}-alb-sg"
vpc_id = var.vpc_id
ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }
egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }
}
resource "aws_security_group" "tasks" {
name = "${var.app_name}-tasks-sg"
vpc_id = var.vpc_id
ingress {
from_port = var.container_port
to_port = var.container_port
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }
}
Con la red aislada correctamente, las tareas nunca quedan expuestas directamente a Internet, solo reciben tráfico del balanceador. Pasemos al corazón del despliegue.
Definir clúster, tarea y servicio en Terraform AWS ECS
Este es el bloque central de Terraform AWS ECS. Creamos el clúster, la definición de tarea (con CPU, memoria e imagen) y el servicio que mantiene el número de réplicas deseado sobre Fargate.
# ecs.tf
resource "aws_ecs_cluster" "main" {
name = "${var.app_name}-cluster"
}
resource "aws_ecs_task_definition" "app" {
family = var.app_name
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 256
memory = 512
execution_role_arn = aws_iam_role.task_execution.arn
container_definitions = jsonencode([{
name = var.app_name
image = var.container_image
essential = true
portMappings = [{ containerPort = var.container_port }]
}])
}
La task_definition es la plantilla de tu contenedor: define qué imagen se ejecuta y con qué recursos. A continuación creamos el servicio, que se encarga de lanzar y mantener vivas las tareas, registrándolas en el balanceador.
# ecs.tf (continuación)
resource "aws_ecs_service" "app" {
name = "${var.app_name}-svc"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
network_configuration {
subnets = var.subnet_ids
security_groups = [aws_security_group.tasks.id]
assign_public_ip = true
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = var.app_name
container_port = var.container_port
}
depends_on = [aws_lb_listener.http]
}
El atributo desired_count conecta con la variable que definimos antes: subir de 2 a 4 réplicas es cambiar un número. El bloque depends_on garantiza que el listener del balanceador exista antes de registrar el servicio.
Exponer la aplicación con un balanceador
Para recibir tráfico necesitamos un Application Load Balancer con su target group y su listener. Este fichero alb.tf completa la ruta desde Internet hasta tus contenedores.
# alb.tf
resource "aws_lb" "app" {
name = "${var.app_name}-alb"
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.subnet_ids
}
resource "aws_lb_target_group" "app" {
name = "${var.app_name}-tg"
port = var.container_port
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check { path = "/" }
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.app.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
Fíjate en target_type = "ip": es obligatorio con Fargate, porque las tareas se registran por su IP en la VPC. Ahora exponemos el DNS del balanceador como salida en outputs.tf para saber dónde acceder.
# outputs.tf
output "alb_dns_name" {
value = aws_lb.app.dns_name
}
Con todo definido, aplica la infraestructura y comprueba el resultado. Terraform te mostrará el plan antes de crear nada.
terraform init
terraform plan -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-a","subnet-b"]'
terraform apply -auto-approve -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-a","subnet-b"]'
Cuando termine, copia el valor de alb_dns_name y ábrelo en el navegador: verás tu contenedor respondiendo tras el balanceador. Acabas de desplegar una arquitectura completa con Terraform AWS ECS.
⚠️ Advertencia de costes
Un ALB y las tareas de Fargate generan coste por hora aunque el tráfico sea mínimo. Para pruebas, ejecuta terraform destroy al terminar y evita dejar el clúster encendido. Revisa siempre el plan antes de aplicar en producción.
Buenas prácticas con Terraform AWS ECS
- Estado remoto: guarda el
terraform.tfstateen un bucket S3 con bloqueo, nunca en local ni en Git. - Módulos reutilizables: encapsula este patrón en un módulo para replicarlo por servicio.
- Autoescalado: añade
aws_appautoscaling_targetpara escalar tareas según CPU o peticiones. - Secretos: inyecta credenciales desde AWS Secrets Manager, no en la task definition.
- HTTPS: añade un certificado ACM y un listener 443 para cifrar el tráfico.
Si quieres seguir profundizando en infraestructura como código, explora más guías en nuestra categoría de tutoriales de Terraform, donde cubrimos redes, IAM, bases de datos y despliegues en Kubernetes.
Solución de problemas frecuentes en Terraform AWS ECS
Estos son los errores más habituales al desplegar Terraform AWS ECS y cómo resolverlos con rapidez para no perder tiempo en producción.
- La tarea no arranca (STOPPED): revisa los logs en CloudWatch; casi siempre es un error al descargar la imagen o un puerto mal mapeado en la task definition.
- El target group marca las tareas como unhealthy: comprueba que el
health_check.pathdevuelve un 200 y que el grupo de seguridad de las tareas permite el tráfico del balanceador. - Error
ResourceInitializationError: falta salida a Internet para descargar la imagen; verificaassign_public_ipo un NAT gateway en subredes privadas. - Permisos denegados: el rol de ejecución no tiene la política
AmazonECSTaskExecutionRolePolicyasociada.
Si un terraform apply falla a mitad, Terraform guarda el estado parcial; corrige el problema y vuelve a aplicar, ya que la herramienta es idempotente y solo creará lo que falte. Con estos ajustes, tu despliegue de Terraform AWS ECS quedará estable y listo para recibir tráfico real.
Conclusión
Has construido un despliegue completo de contenedores con Terraform AWS ECS y Fargate: red aislada, roles de mínimo privilegio, definición de tarea, servicio con réplicas y balanceador. La gran ventaja es que todo vive en código versionado, así que reproducir, auditar o destruir este entorno es cuestión de un comando. A partir de aquí, encapsúlalo en módulos y añade autoescalado y HTTPS para llevarlo a producción con garantías. Dominar este patrón de Terraform AWS ECS te permitirá desplegar cualquier microservicio en contenedores de forma consistente y repetible en todos tus entornos.
Preguntas frecuentes sobre Terraform AWS ECS
¿Fargate o EC2 para ECS con Terraform?
Fargate es ideal cuando no quieres gestionar servidores: pagas por tarea y AWS se encarga del sustrato. EC2 conviene si necesitas control fino del host o cargas muy constantes donde el coste por instancia reservada sale más barato.
¿Puedo usar OpenTofu en lugar de Terraform?
Sí. El código mostrado es compatible con OpenTofu, el fork open source. Solo cambia el binario que ejecutas; los proveedores y la sintaxis HCL son los mismos.
¿Cómo actualizo la imagen del contenedor?
Cambia la variable container_image y ejecuta terraform apply. ECS creará una nueva revisión de la task definition y desplegará las tareas de forma progresiva sin cortar el servicio.
¿Necesito una VPC propia?
Necesitas una VPC con al menos dos subredes en zonas distintas. Puedes crearla también con Terraform y pasar sus identificadores como variables a este proyecto de ECS.
Recursos y documentación oficial
- Recurso aws_ecs_service — Terraform Registry
- AWS Fargate — Documentación oficial
- Documentación de Terraform
- Amazon ECS Developer Guide
- Documentación de OpenTofu
