Your containerized application with IAC on AWS — Pt.2
Published: 2024-05-03 18:08:48

Hi everyone! We’ll see how to create our terraform modules in this blog article. Next, we’ll publish our application to AWS Fargate using the terraform modules we created here and also terragrunt.

**TERRAFORM**
We will establish our directory structure and our terraform module scripts in this blog article. We will set everything up and utilize terraform in conjunction with Terragrunt in part 3.

**DIRECTORIES**
Our codes must be organized at the directory level in order to use terraform and terragrunt:

```
app
modules
    ├── amazon_vpc
    ├── aws_loadbalancer
    ├── aws_fargate
    ├── aws_roles
    ├── aws_ecs_cluster
    └── aws_targetgroup
    └── aws_certificate_manager
```



```
terragrunt
    └── dev
        └── us-east-1
            ├── aws_ecs
            │   ├── cluster
            │   └── service
            ├── aws_loadbalancer
            ├── amazon_vpc
            ├── aws_targetgroup
            ├── aws_roles
            ├── aws_certificate_manager
            └── terragrunt.hcl
```

- **app**: This is our infrastructure’s primary directory.
- **modules**: Each unique AWS resource or service has a subdirectory within this directory. The modules will be inserted here, arranged according to resources like VPC, load balancers, ECS, etc.
- **Terraform** **subdirectories**: Module-specific Terraform files are located in subdirectories like amazon_vpc and aws_loadbalancer.
- **Terragrunt**: Terragrunt configurations are kept in this directory.
- **dev**: Stands for the configuration of the development environment.
- **us-east-1**: Configurations unique to the AWS region “us-east-1”.
- **Terragrunt subdirectories**: Environment- and region-specific options for individual services may be found in the aws_ecs, aws_loadbalancer, amazon_vpc, etc. folders.
- **terragrunt.hcl**: This is our Terragrunt configuration file, where we will include backend configurations as well as those that apply to all services in the “us-east-1” area of the development environment.
-** Modules have three files**: variables.tf, main.tf, and _outputs.tf in each of the subdirectories. Roles will make use of a _data.tf
- **main.tf**: The main.tf file, which defines and configures AWS resources, is the hub of the module.
- **variables.tf**: Allows for module customisation and reuse by defining variables that the module will use.
- **_outputs.tf**: Indicates which module outputs — information — will be accessible to other modules or the Terraform project in its whole.
- **_data.tf**: To consult and look up information on already-existing resources or services, we shall utilize data.

**RESOURCES**
The following are the AWS resources that we will use:

- VPC
- SUBNETS
- ROUTE TABLE
- INTERNET GATEWAY
- NAT GATEWAY
- ELASTIC IP
- ECR
- SECURITY GROUP
- APPLICATION LOAD BALANCER
- FARGATE
- ROUTE53
- ACM
- TERRAFORM MODULES

**VPC**
Let’s get started with VPC module creation. It will be necessary for each and every one of our apps’ network connections.

```
modules
 ├── amazon_vpc
```

_**main.tf**_

```
// Creat VPC
resource "aws_vpc" "vpc" {
  cidr_block           = var.vpc_cidr_block
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-vpc"
    },
    var.tags,
  )
}


// Creat public subnet1 for VPC
resource "aws_subnet" "public_subnet1" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = var.public_subnet1_cidr_block
  availability_zone = var.availability_zone1

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-public-subnet1"
    },
    var.tags,
  )
}


// Creat public subnet2 for VPC
resource "aws_subnet" "public_subnet2" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = var.public_subnet2_cidr_block
  availability_zone = var.availability_zone2

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-public-subnet2"
    },
    var.tags,
  )
}


// Creat private subnet1 for VPC
resource "aws_subnet" "private_subnet1" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = var.private_subnet1_cidr_block
  availability_zone = var.availability_zone1

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-private-subnet1"
    },
    var.tags,
  )
}


// Creat private subnet2 for VPC
resource "aws_subnet" "private_subnet2" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = var.private_subnet2_cidr_block
  availability_zone = var.availability_zone2

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-private-subnet2"
    },
    var.tags,
  )
}


// Create Internet gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}"
    },
    var.tags,
  )
}


// Creat route IGW VPC default rtb
resource "aws_default_route_table" "vpc_default_rtb" {
  default_route_table_id = aws_vpc.vpc.default_route_table_id

  # Internet gtw route
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-vpc-default-rtb"
    },
    var.tags,
  )
}


// Associate a public subnet1 with VPC
resource "aws_route_table_association" "public_subnet1_rtb_association" {
  subnet_id      = aws_subnet.public_subnet1.id
  route_table_id = aws_default_route_table.vpc_default_rtb.id
}

# Associate public subnet2 with VPC
resource "aws_route_table_association" "public_subnet2_rtb_association" {
  subnet_id      = aws_subnet.public_subnet2.id
  route_table_id = aws_default_route_table.vpc_default_rtb.id
}

# Create custom private route table 1 
resource "aws_route_table" "private_rtb1" {
  vpc_id = aws_vpc.vpc.id

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-rtb1"
    },
    var.tags,
  )
}


// Creat custom private route table 2
resource "aws_route_table" "private_rtb2" {
  vpc_id = aws_vpc.vpc.id

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-rtb2"
    },
    var.tags,
  )
}


// Creat EIP for nat1
resource "aws_eip" "eip1" {
  domain = "vpc"

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-eip1"
    },
    var.tags,
  )
}


// Creat EIP for nat2
resource "aws_eip" "eip2" {
  domain = "vpc"

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-eip2"
    },
    var.tags,
  )
}


// Creat NAT GTW1
resource "aws_nat_gateway" "nat_gtw1" {
  allocation_id = aws_eip.eip1.id
  subnet_id     = aws_subnet.public_subnet1.id

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-nat-gtw1"
    },
    var.tags,
  )
}


// Creat NAT GTW2
resource "aws_nat_gateway" "nat_gtw2" {
  allocation_id = aws_eip.eip2.id
  subnet_id     = aws_subnet.public_subnet2.id

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}-nat-gtw2"
    },
    var.tags,
  )
}


// Configure natgtw route private route table 1
resource "aws_route" "private_rtb1_nat_gtw1" {
  route_table_id         = aws_route_table.private_rtb1.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat_gtw1.id
}



// Configure nat gtw route private route table 2
resource "aws_route" "private_rtb2_nat_gtw2" {
  route_table_id         = aws_route_table.private_rtb2.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat_gtw2.id
}



// Associate private subnet1 VPC
resource "aws_route_table_association" "private_subnet1_rtb_association" {
  subnet_id      = aws_subnet.private_subnet1.id
  route_table_id = aws_route_table.private_rtb1.id
}



// Associate private subnet2 VPC
resource "aws_route_table_association" "private_subnet2_rtb_association" {
  subnet_id      = aws_subnet.private_subnet2.id
  route_table_id = aws_route_table.private_rtb2.id
}



resource "aws_security_group" "default" {
  name        = "${var.env}-${var.project_name}-sg-vpc"
  description = "Default security group to allow inbound/outbound from the VPC"
  vpc_id      = "${aws_vpc.vpc.id}"

  ingress {
    from_port = "0"
    to_port   = "0"
    protocol  = "-1"
    self      = true
  }

  egress {
    from_port = "0"
    to_port   = "0"
    protocol  = "-1"
    self      = "true"
  }
}
```
_**variables.tf**_

```
variable "vpc_cidr_block" {
}

variable "public_subnet1_cidr_block" {
}

variable "public_subnet2_cidr_block" {
}

variable "private_subnet1_cidr_block" {
}

variable "private_subnet2_cidr_block" {
}

variable "availability_zone1" {
}

variable "availability_zone2" {
}

variable "project_name" {
}

variable "env" {
}

variable "tags" {
  type = map(string)
}

```

**__outputs.tf_**

```
output "vpc_arn" {
  value = aws_vpc.vpc.arn
}

output "vpc_id" {
  value = aws_vpc.vpc.id
}

output "vpc_main_rtb" {
  value = aws_vpc.vpc.main_route_table_id
}

output "vpc_cidr_block" {
  value = aws_vpc.vpc.cidr_block
}


output "public_subnet1_id" {
  value = aws_subnet.public_subnet1.id
}

output "public_subnet1_cidr_block" {
  value = aws_subnet.public_subnet1.cidr_block
}

output "public_subnet1_az" {
  value = aws_subnet.public_subnet1.availability_zone
}

output "public_subnet1_az_id" {
  value = aws_subnet.public_subnet1.availability_zone_id
}


output "public_subnet2_id" {
  value = aws_subnet.public_subnet2.id
}

output "public_subnet2_cidr_block" {
  value = aws_subnet.public_subnet2.cidr_block
}

output "public_subnet2" {
  value = aws_subnet.public_subnet2.availability_zone
}

output "public_subnet2_az_id" {
  value = aws_subnet.public_subnet2.availability_zone_id
}

output "private_subnet1_id" {
  value = aws_subnet.private_subnet1.id
}

output "private_subnet1_cidr_block" {
  value = aws_subnet.private_subnet1.cidr_block
}

output "private_subnet1_az" {
  value = aws_subnet.private_subnet1.availability_zone
}

output "private_subnet1_az_id" {
  value = aws_subnet.private_subnet1.availability_zone_id
}

output "private_subnet2_id" {
  value = aws_subnet.private_subnet2.id
}

output "private_subnet2_cidr_block" {
  value = aws_subnet.private_subnet2.cidr_block
}

output "private_subnet2_az" {
  value = aws_subnet.private_subnet2.availability_zone
}

output "private_subnet2_az_id" {
  value = aws_subnet.public_subnet2.availability_zone_id
}

output "igw_id" {
  value = aws_internet_gateway.igw.id
}

output "default_rtb_id" {
  value = aws_default_route_table.vpc_default_rtb.id
}
```
---

**IAM PERMISSIONS**
We need to create permissions for our services.

```
modules
 ├── aws_roles
```
_**_data.tf**_

```
data "aws_iam_policy_document" "ecs_service_role" {
  statement {
    actions = [
      "application-autoscaling:DeleteScalingPolicy",
      "application-autoscaling:DeregisterScalableTarget",
      "application-autoscaling:DescribeScalableTargets",
      "application-autoscaling:DescribeScalingActivities",
      "application-autoscaling:DescribeScalingPolicies",
      "application-autoscaling:PutScalingPolicy",
      "application-autoscaling:RegisterScalableTarget",
      "autoscaling:UpdateAutoScalingGroup",
      "autoscaling:CreateAutoScalingGroup",
      "autoscaling:CreateLaunchConfiguration",
      "autoscaling:DeleteAutoScalingGroup",
      "autoscaling:DeleteLaunchConfiguration",
      "autoscaling:Describe*",
      "ec2:CreateNetworkInterface",
      "ec2:DescribeDhcpOptions",
      "ec2:DescribeNetworkInterfaces",
      "ec2:DeleteNetworkInterface",
      "ec2:DescribeSubnets",
      "ec2:DescribeSecurityGroups",
      "ec2:DescribeVpcs",
      "ec2:AssociateRouteTable",
      "ec2:AttachInternetGateway",
      "ec2:AuthorizeSecurityGroupIngress",
      "ec2:CancelSpotFleetRequests",
      "ec2:CreateInternetGateway",
      "ec2:CreateLaunchTemplate",
      "ec2:CreateRoute",
      "ec2:CreateRouteTable",
      "ec2:CreateSecurityGroup",
      "ec2:CreateSubnet",
      "ec2:CreateVpc",
      "ec2:DeleteLaunchTemplate",
      "ec2:DeleteSubnet",
      "ec2:DeleteVpc",
      "ec2:Describe*",
      "ec2:DetachInternetGateway",
      "ec2:DisassociateRouteTable",
      "ec2:ModifySubnetAttribute",
      "ec2:ModifyVpcAttribute",
      "ec2:RunInstances",
      "ec2:RequestSpotFleet",
      "codebuild:BatchGetBuilds",
      "codebuild:StartBuild",
      "s3:GetObject",
      "s3:GetObjectVersion",
      "s3:GetBucketVersioning",
      "s3:PutObject",
      "s3:PutObjectAcl",
      "s3:ListBucket",
      "es:ESHttpPost",
      "ecr:*",
      "ecs:*",
      "ec2:*",
      "sqs:*",
      "cloudwatch:*",
      "logs:*",
      "iam:PassRole",
      "elasticloadbalancing:Describe*",
      "iam:AttachRolePolicy",
      "iam:CreateRole",
      "iam:GetPolicy",
      "iam:GetPolicyVersion",
      "iam:GetRole",
      "iam:ListAttachedRolePolicies",
      "iam:ListRoles",
      "iam:ListGroups",
      "iam:ListUsers",
      "iam:ListInstanceProfiles",
      "elasticfilesystem:*",
      "secretsmanager:GetSecretValue",
      "ssm:GetParameters",
      "ssm:GetParameter",
      "ssm:GetParametersByPath",
      "kms:Decrypt",
      "dynamodb:GetItem",
      "dynamodb:PutItem",
      "dynamodb:UpdateItem",
      "dynamodb:DeleteItem",
      "dynamodb:Query",
      "dynamodb:Scan",
    ]

    sid       = "1"
    effect    = "Allow"
    resources = ["*"]
  }
}
```

_**main.tf**_

```
// Creat policy
resource "aws_iam_policy" "ecs_service_policy" {
  name   = "${var.env}-${var.project_name}-policy"
  path   = "/"
  policy = data.aws_iam_policy_document.ecs_service_role.json
}

// Creat IAM Role
resource "aws_iam_role" "ecs_service_role" {
  name                  = "${var.env}-${var.project_name}-role"
  force_detach_policies = "true"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "1",
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": [
          "ecs.amazonaws.com",
          "ecs-tasks.amazonaws.com",
          "codebuild.amazonaws.com",
          "codepipeline.amazonaws.com",
          "ecs.application-autoscaling.amazonaws.com",
          "ec2.amazonaws.com",
          "ecr.amazonaws.com"
        ]
      }
    }
  ]
}
EOF

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}"
    },
    var.tags,
  )
}

resource "aws_iam_policy_attachment" "ecs_service_role_atachment_policy" {
  name       = "${var.env}-${var.project_name}-policy-attachment"
  roles      = [aws_iam_role.ecs_service_role.name]
  policy_arn = aws_iam_policy.ecs_service_policy.arn
}
```
_**variables.tf**_

```
variable "env" {
}

variable "project_name" {
}


variable "tags" {
    type        = map(string)
  default     = {}
}
```
**__outputs.tf_**

```
output ecs_role_arn {
    value = aws_iam_role.ecs_service_role.arn
}
```

---

**AWS CERTIFICATE MANAGER**

```
modules
 ├── aws_certificate_manager
```

We will also need a domain already configured in a zone hosted on AWS. With the domain created, we will create a valid TLS certificate within our account.

_**main.tf**_

```
// creat the certificate
resource "aws_acm_certificate" "cert" {
  domain_name       = "*.${var.domain_name}"
  validation_method = "DNS"

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}"
    },
    var.tags,
  )
  lifecycle {
    create_before_destroy = true
  }
}


// validation certificate
resource "aws_route53_record" "record_certificate_validation" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = "Z08676461KWRT5RHNLSKS"
}
```
_**variables.tf**_

```
variable "env" {
}

variable "domain_name" {
}

variable "project_name" {
}

variable "tags" {
  type    = map(string)
  default = {}
}
```
_**_outputs.tf**_

```
output "acm_arn" {
  value = aws_acm_certificate.cert.arn
}
```

---
**_AWS LOAD BALANCER_**
Here, we will create an application load balancer that will handle the balancing of our applications.

```
modules
 ├── aws_loadbalancer
```
_**main.tf**_

```
// Creat AWS ALB 
resource "aws_lb" "alb" {
  load_balancer_type         = "application"
  internal                   = var.alb_internal
  name                       = "${var.env}-alb-${var.project_name}"
  subnets                    = ["${var.subnet_id_1}", "${var.subnet_id_2}"]
  drop_invalid_header_fields = var.alb_drop_invalid_header_fields

  security_groups = [
    aws_security_group.alb.id,
  ]

  idle_timeout = 400

  dynamic "access_logs" {
    for_each = compact([var.lb_access_logs_bucket])

    content {
      bucket  = var.lb_access_logs_bucket
      prefix  = var.lb_access_logs_prefix
      enabled = true
    }
  }

  tags = {
    Name = "${var.env}-alb-${var.project_name}"
  }
}



//Creat SG ALB
resource "aws_security_group" "alb" {
  name        = "${var.env}-sg-alb-${var.project_name}"
  description = "SG for ECS ALB"
  vpc_id      = var.vpc_id

  revoke_rules_on_delete = "true"

  ingress {
    description = "TLS from VPC"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]

  }

  ingress {
    description = "HTTP from VPC"
    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"]
  }


  tags = {
    Name = "${var.env}-alb-${var.project_name}"
  }
}

//Creat default TG - ALB
resource "aws_alb_target_group" "target_group" {
  name        = "${var.env}-tg-default-alb"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = var.vpc_id

  lifecycle {
    create_before_destroy = true
  }

  tags = merge(
    {
      "Name" = "${var.env}-tg-${var.project_name}"
    },
    var.tags,
  )
}


// Creat HTTPS listener
resource "aws_alb_listener" "listener_ssl" {
  load_balancer_arn = aws_lb.alb.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = var.certificate_arn

  default_action {
    target_group_arn = aws_alb_target_group.target_group.arn
    type             = "forward"
  }
  depends_on = [
    aws_alb_target_group.target_group
  ]
}


resource "aws_alb_listener_rule" "ssl_listener_rule" {
  action {
    target_group_arn = aws_alb_target_group.target_group.arn
    type             = "forward"
  }

  condition {
    host_header {
      values = ["default.${var.domain_name}"]
    }
  }

  priority     = var.priority_listener_rule
  listener_arn = aws_alb_listener.listener_ssl.arn

  depends_on = [
    aws_alb_listener.listener_ssl,
    aws_alb_target_group.target_group
  ]
}


// Creat HTTP listener
resource "aws_lb_listener" "listener_http" {
  load_balancer_arn = aws_lb.alb.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }

} 
```
_**variables.tf**_

```
variable "alb" {
  default = true
}

variable "alb_http_listener" {
  default = true
}

variable "alb_sg_allow_test_listener" {
  default = true
}

variable "alb_sg_allow_egress_https_world" {
  default = true
}

variable "alb_only" {
  default = false
}

variable "alb_ssl_policy" {
  default = "ELBSecurityPolicy-2016-08"
  type    = string
}

variable "alb_internal_ssl_policy" {
  default = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
  type    = string
}

variable "alb_drop_invalid_header_fields" {
  default = true
  type    = bool
}

variable "lb_access_logs_bucket" {
  type    = string
  default = ""
}

variable "lb_access_logs_prefix" {
  type    = string
  default = ""
}

variable "vpc_id" {
  type    = string
  default = ""
}

variable "subnet_id_1" {
  type    = string
  default = ""
}

variable "subnet_id_2" {
  type    = string
  default = ""
}

variable "project_name" {
  type    = string
  default = ""
}

variable "env" {
  type    = string
  default = ""
}

variable "alb_internal" {
  type    = bool
  default = false
}

variable "certificate_arn" {
  type    = string
  default = ""
}

variable "tags" {
  type    = map(string)
  default = {}
}

variable "priority_listener_rule" {
}

variable "domain_name" {
}
```
**_outputs.tf_**


```
output "alb_arn" {
  value = aws_lb.alb.arn
}

output "alb_dns_name" {
  value = aws_lb.alb.dns_name
}


output "alb_secgrp_id" {
  value = aws_security_group.alb.id
}


output "alb_arn_suffix" {
  value = trimspace(regex(".*loadbalancer/(.*)", aws_lb.alb.arn)[0])
}

output "listener_ssl_arn" {
  value = aws_alb_listener.listener_ssl.arn
}
```
---

**AWS TARGET GROUP**
Moving forward, let’s look at the codes that will comprise our TG.

```
modules
 ├── aws_targetgroup
```
_**main.tf**_

```
//Creat Target Group
resource "aws_alb_target_group" "target_group" {
  name        = "${var.env}-tg-${var.project_name}"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = var.vpc_id

  health_check {
    matcher             = "200-299"
    path                = var.health_check_path
    port                = var.container_port
    protocol            = "HTTP"
    unhealthy_threshold = 8
    timeout             = 10
  }


  lifecycle {
    create_before_destroy = true
  }

  tags = merge(
    {
      "Name" = "${var.env}-tg-${var.project_name}"
    },
    var.tags,
  )
}



// Creat HTTPS listener rule
resource "aws_alb_listener_rule" "ssl_listener_rule" {
  action {
    target_group_arn = aws_alb_target_group.target_group.arn
    type             = "forward"
  }

  condition {
    host_header {
      values = ["${var.host_headers}"]
    }
  }

  priority     = var.priority_listener_rule
  listener_arn = var.listener_ssl_arn

}
```
_**variables.tf**_

```
variable "project_name" {
}

variable "env" {
}

variable "certificate_arn" {
}

variable "tags" {
  description = "Mapa de tags para serem aplicadas aos recursos."
  type        = map(string)
  default     = {}
}

variable "vpc_id" {
}

variable "subnet_id_1" {
}

variable "subnet_id_2" {
}

variable "listener_ssl_arn" {
}

variable "priority_listener_rule" {
}

variable "host_headers" {
}

variable "health_check_path" {
}

variable "container_port" {
}
```
_**_outputs.tf**_

```
output "tg_alb_arn" {
  value = aws_alb_target_group.target_group.arn
}

output "tg_arn_suffix" {
  value = regex(".*:(.*)", aws_alb_target_group.target_group.arn)[0]
}
```
---

**ECS and ECR**
All of the container configurations will be made here. We will build an ECS cluster first, and then a fargate service with all the necessary components. To host our application image, we will construct a repository in ECR in addition to the cluster and service.

```
ECS CLUSTER
modules
 ├── aws_cluster
```
_**main.tf**_

```
// Creat ECS cluster ECS 
resource "aws_ecs_cluster" "ecs" {
  name = "${var.env}-${var.project_name}"

  setting {
    name  = "containerInsights"
    value = var.container_insights ? "enabled" : "disabled"
  }

  lifecycle {
    ignore_changes = [
      tags
    ]
  }

}
```
_**variables.tf**_

```
variable "project_name" {
  type        = string
  default     = ""
}

variable "env" {
  type        = string
  default     = ""
}

variable "container_insights" {
  type        = bool
  default     = false
}
```
_**_outputs.tf**_

```
output "cluster_name" {
  value = aws_ecs_cluster.ecs.name
}

output "cluster_arn" {
  value = aws_ecs_cluster.ecs.arn
}
```
**FARGATE**

```
modules
 ├── aws_fargate
```
_**main.tf**_

```
//Creat ECR repositpry
resource "aws_ecr_repository" "ecs_cluster_ecr" {
  name = "${var.env}-${var.project_name}"

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}"
    },
    var.tags,
  )
}

//Creat Route53 record 
resource "aws_route53_record" "record_sonic" {
  zone_id = "Z08676461KWRT5RHNLSKS"
  name    = "${var.host_headers}"
  type    = "CNAME"
  ttl     = 300
  records = [var.alb_dns_name]
}

//Creat Task Definition
resource "aws_ecs_task_definition" "ecs_task_definition" {
  family = "${var.env}-task-def-${var.project_name}"

  container_definitions = <<DEFINITION
[
  {
    "name":  "${var.env}-${var.project_name}" ,
    "image": "${var.aws_account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.env}-${var.project_name}:latest",
    "essential": true,
    "memoryReservation": 64,
    "portMappings": [{
      "containerPort": ${var.container_port}
    }],
    "environment": [
      {
        "name": "ENV_PORT",
        "value": "${var.container_port}"
      },
      {
        "name": "ENVIRONMENT",
        "value": "${var.env}"
      }
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "ecs-${var.env}-${var.project_name}",
        "awslogs-region": "${var.region}",
        "awslogs-create-group": "true",
        "awslogs-stream-prefix": "${var.env}-${var.project_name}"
      }
    }
  }
]

DEFINITION


  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  task_role_arn            = var.ecs_role_arn
  execution_role_arn       = var.ecs_role_arn
  cpu                      = var.container_vcpu
  memory                   = var.container_memory
}


//Creat Fargate Service
resource "aws_ecs_service" "ecs_service" {
  name            = "${var.env}-${var.project_name}-service"
  cluster         = "${var.cluster_arn}"
  task_definition = aws_ecs_task_definition.ecs_task_definition.arn
  desired_count   = var.instance_count
  launch_type     = "FARGATE"

  load_balancer {
    target_group_arn = var.target_group_arn
    container_name   = "${var.env}-${var.project_name}"
    container_port   = var.container_port
  }

  network_configuration {
    security_groups  = [aws_security_group.sg_ecs.id]
    subnets          = ["${var.subnet_id_1}", "${var.subnet_id_2}"]
    assign_public_ip = "false"
  }

  deployment_minimum_healthy_percent = 50
  deployment_maximum_percent         = 400

  tags = merge(
    {
      "Name" = "${var.env}-${var.project_name}"
    },
    var.tags,
  )
}



///Creat SG to ECS
resource "aws_security_group" "sg_ecs" {
  name                   = "${var.env}-sg-ecs-${var.project_name}"
  description            = "SG for ECS"
  vpc_id                 = var.vpc_id
  revoke_rules_on_delete = "true"

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.env}-sg-ecs-${var.project_name}"
  }
}

// SG rule ALB
resource "aws_security_group_rule" "rule_ecs_alb" {
  description              = "from ALB"
  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = "-1"
  security_group_id        = aws_security_group.sg_ecs.id
  source_security_group_id = var.sg_alb
}

// SG rule ECS
resource "aws_security_group_rule" "in_ecs_nodes" {
  description              = "from ECS"
  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = "-1"
  security_group_id        = aws_security_group.sg_ecs.id
  source_security_group_id = aws_security_group.sg_ecs.id
}
```
**_variables.tf_**

```
variable "env" {
}

variable "region" {
}

variable "project_name" {
}

variable "container_port" {
}

variable "instance_count" {
}

variable "container_vcpu" {
}

variable "container_memory" {
}

variable "vpc_id" {
}

variable "subnet_id_1" {
}

variable "subnet_id_2" {
}

variable "aws_account_id" {
}

variable "tags" {
  type    = map(string)
  default = {}
}

variable "ecs_role_arn" {
}

variable "target_group_arn" {
}

variable "sg_alb" {
}

variable "cluster_arn" {
}


variable "host_headers" {
}

variable "alb_dns_name" {
}

```
_**_outputs.tf**_

```
output "sg_ecs" {
  value = aws_security_group.sg_ecs.id
}

output "service_name" {
  value = aws_ecs_service.ecs_service.name
}
```

---
Our modules are ready, and in the next section, we will create the hcl for Terragrunt and also apply our code. See Ya!