<> = terraform = Terraform is as open source tool to create, change and manage infrastructure through code (IaC infrastructure as code). It acts as a infrastructure blueprint. == main.tf for ECS with cherrypy in ECS and postgres in RDS == It sets a public IP for the ECS service and tasks.Using the '''assign_public_ip=true''' . The RDS DB uses '''publicly_accessible = false''' which makes it inaccessible publicly. Other used services: * VPC * ECR * IAM * CloudWatch logs * SecurityGroups {{{#!highlight sh # Provider definition terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = var.aws_region } # Variables variable "aws_region" { type = string default = "us-east-1" } variable "db_password" { type = string description = "PostgreSQL database master password" sensitive = true default = "SuperSecurePassword123!" } # --- NETWORKING (VPC) --- data "aws_availability_zones" "available" { state = "available" } resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true enable_dns_support = true tags = { Name = "cherrypy-ecs-vpc" } } resource "aws_subnet" "public_1" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = data.aws_availability_zones.available.names[0] map_public_ip_on_launch = true tags = { Name = "cherrypy-public-1" } } resource "aws_subnet" "public_2" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" availability_zone = data.aws_availability_zones.available.names[1] map_public_ip_on_launch = true tags = { Name = "cherrypy-public-2" } } resource "aws_internet_gateway" "gw" { vpc_id = aws_vpc.main.id tags = { Name = "cherrypy-igw" } } resource "aws_route_table" "public" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.gw.id } tags = { Name = "cherrypy-public-rt" } } resource "aws_route_table_association" "public_1" { subnet_id = aws_subnet.public_1.id route_table_id = aws_route_table.public.id } resource "aws_route_table_association" "public_2" { subnet_id = aws_subnet.public_2.id route_table_id = aws_route_table.public.id } # --- SECURITY GROUPS --- resource "aws_security_group" "ecs_tasks" { name = "cherrypy-ecs-tasks-sg" description = "Allow inbound traffic on port 8080" vpc_id = aws_vpc.main.id ingress { protocol = "tcp" from_port = 8080 to_port = 8080 cidr_blocks = ["0.0.0.0/0"] # For testing. In production, restrict via a Load Balancer. } egress { protocol = "-1" from_port = 0 to_port = 0 cidr_blocks = ["0.0.0.0/0"] } } resource "aws_security_group" "db" { name = "cherrypy-db-sg" description = "Allow inbound traffic from ECS tasks only" vpc_id = aws_vpc.main.id ingress { protocol = "tcp" from_port = 5432 to_port = 5432 security_groups = [aws_security_group.ecs_tasks.id] } egress { protocol = "-1" from_port = 0 to_port = 0 cidr_blocks = ["0.0.0.0/0"] } } # --- DATABASE (RDS PostgreSQL) --- resource "aws_db_subnet_group" "main" { name = "cherrypy-db-subnet-group" subnet_ids = [aws_subnet.public_1.id, aws_subnet.public_2.id] } resource "aws_db_instance" "postgres" { identifier = "cherrypy-postgres-db" allocated_storage = 20 engine = "postgres" engine_version = "15" instance_class = "db.t4g.micro" db_name = "cherrypy_db" username = "cherrypy_user" password = var.db_password db_subnet_group_name = aws_db_subnet_group.main.name vpc_security_group_ids = [aws_security_group.db.id] skip_final_snapshot = true } # --- CONTAINER REGISTRY (ECR) --- resource "aws_ecr_repository" "app" { name = "cherrypy-app" image_tag_mutability = "MUTABLE" image_scanning_configuration { scan_on_push = true } } # --- IAM ROLES FOR ECS --- resource "aws_iam_role" "ecs_execution_role" { name = "cherrypy-ecs-execution-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ecs-tasks.amazonaws.com" } }] }) } resource "aws_iam_role_policy_attachment" "ecs_execution" { role = aws_iam_role.ecs_execution_role.name policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } # --- CONTAINER ORCHESTRATION (ECS) --- resource "aws_ecs_cluster" "main" { name = "cherrypy-ecs-cluster" } resource "aws_ecs_task_definition" "app" { family = "cherrypy-task" network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] cpu = "256" memory = "512" execution_role_arn = aws_iam_role.ecs_execution_role.arn container_definitions = jsonencode([{ name = "cherrypy-container" image = "${aws_ecr_repository.app.repository_url}:latest" essential = true portMappings = [{ containerPort = 8080 hostPort = 8080 }] environment = [ { name = "DB_HOST", value = aws_db_instance.postgres.address }, { name = "DB_NAME", value = aws_db_instance.postgres.db_name }, { name = "DB_USER", value = aws_db_instance.postgres.username }, { name = "DB_PASS", value = var.db_password } ] }]) } resource "aws_ecs_service" "main" { name = "cherrypy-service" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.app.arn desired_count = 1 launch_type = "FARGATE" network_configuration { subnets = [aws_subnet.public_1.id, aws_subnet.public_2.id] security_groups = [aws_security_group.ecs_tasks.id] assign_public_ip = true } } # --- OUTPUTS --- output "ecr_repository_url" { value = aws_ecr_repository.app.repository_url description = "Use URL to push the CherryPy Docker image" } output "db_endpoint" { value = aws_db_instance.postgres.endpoint description = "PostgreSQL database endpoint" } }}} {{{#!highlight sh sudo apk add aws-cli --repository=https://alpinelinux.org aws --version aws configure sudo apk add terraform --repository=https://alpinelinux.org terraform --version terraform init terraform validate terraform plan terraform applu aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ://amazonaws.com docker build -t cherrypy-app . docker tag cherrypy-app:latest ://amazonaws.com docker push ://amazonaws.com }}}