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:

   1 # Provider definition
   2 terraform {
   3   required_providers {
   4     aws = {
   5       source  = "hashicorp/aws"
   6       version = "~> 5.0"
   7     }
   8   }
   9 }
  10 
  11 provider "aws" {
  12   region = var.aws_region
  13 }
  14 
  15 # Variables
  16 variable "aws_region" {
  17   type    = string
  18   default = "us-east-1"
  19 }
  20 
  21 variable "db_password" {
  22   type        = string
  23   description = "PostgreSQL database master password"
  24   sensitive   = true
  25   default     = "SuperSecurePassword123!"
  26 }
  27 
  28 # --- NETWORKING (VPC) ---
  29 data "aws_availability_zones" "available" {
  30   state = "available"
  31 }
  32 
  33 resource "aws_vpc" "main" {
  34   cidr_block           = "10.0.0.0/16"
  35   enable_dns_hostnames = true
  36   enable_dns_support   = true
  37   tags = { Name = "cherrypy-ecs-vpc" }
  38 }
  39 
  40 resource "aws_subnet" "public_1" {
  41   vpc_id            = aws_vpc.main.id
  42   cidr_block        = "10.0.1.0/24"
  43   availability_zone = data.aws_availability_zones.available.names[0]
  44   map_public_ip_on_launch = true
  45   tags = { Name = "cherrypy-public-1" }
  46 }
  47 
  48 resource "aws_subnet" "public_2" {
  49   vpc_id            = aws_vpc.main.id
  50   cidr_block        = "10.0.2.0/24"
  51   availability_zone = data.aws_availability_zones.available.names[1]
  52   map_public_ip_on_launch = true
  53   tags = { Name = "cherrypy-public-2" }
  54 }
  55 
  56 resource "aws_internet_gateway" "gw" {
  57   vpc_id = aws_vpc.main.id
  58   tags   = { Name = "cherrypy-igw" }
  59 }
  60 
  61 resource "aws_route_table" "public" {
  62   vpc_id = aws_vpc.main.id
  63   route {
  64     cidr_block = "0.0.0.0/0"
  65     gateway_id = aws_internet_gateway.gw.id
  66   }
  67   tags = { Name = "cherrypy-public-rt" }
  68 }
  69 
  70 resource "aws_route_table_association" "public_1" {
  71   subnet_id      = aws_subnet.public_1.id
  72   route_table_id = aws_route_table.public.id
  73 }
  74 
  75 resource "aws_route_table_association" "public_2" {
  76   subnet_id      = aws_subnet.public_2.id
  77   route_table_id = aws_route_table.public.id
  78 }
  79 
  80 # --- SECURITY GROUPS ---
  81 resource "aws_security_group" "ecs_tasks" {
  82   name        = "cherrypy-ecs-tasks-sg"
  83   description = "Allow inbound traffic on port 8080"
  84   vpc_id      = aws_vpc.main.id
  85 
  86   ingress {
  87     protocol    = "tcp"
  88     from_port   = 8080
  89     to_port     = 8080
  90     cidr_blocks = ["0.0.0.0/0"] # For testing. In production, restrict via a Load Balancer.
  91   }
  92 
  93   egress {
  94     protocol    = "-1"
  95     from_port   = 0
  96     to_port     = 0
  97     cidr_blocks = ["0.0.0.0/0"]
  98   }
  99 }
 100 
 101 resource "aws_security_group" "db" {
 102   name        = "cherrypy-db-sg"
 103   description = "Allow inbound traffic from ECS tasks only"
 104   vpc_id      = aws_vpc.main.id
 105 
 106   ingress {
 107     protocol        = "tcp"
 108     from_port       = 5432
 109     to_port         = 5432
 110     security_groups = [aws_security_group.ecs_tasks.id]
 111   }
 112 
 113   egress {
 114     protocol    = "-1"
 115     from_port   = 0
 116     to_port     = 0
 117     cidr_blocks = ["0.0.0.0/0"]
 118   }
 119 }
 120 
 121 # --- DATABASE (RDS PostgreSQL) ---
 122 resource "aws_db_subnet_group" "main" {
 123   name       = "cherrypy-db-subnet-group"
 124   subnet_ids = [aws_subnet.public_1.id, aws_subnet.public_2.id]
 125 }
 126 
 127 resource "aws_db_instance" "postgres" {
 128   identifier             = "cherrypy-postgres-db"
 129   allocated_storage      = 20
 130   engine                 = "postgres"
 131   engine_version         = "15"
 132   instance_class         = "db.t4g.micro"
 133   db_name                = "cherrypy_db"
 134   username               = "cherrypy_user"
 135   password               = var.db_password
 136   db_subnet_group_name   = aws_db_subnet_group.main.name
 137   vpc_security_group_ids = [aws_security_group.db.id]
 138   skip_final_snapshot    = true
 139 }
 140 
 141 # --- CONTAINER REGISTRY (ECR) ---
 142 resource "aws_ecr_repository" "app" {
 143   name                 = "cherrypy-app"
 144   image_tag_mutability = "MUTABLE"
 145   image_scanning_configuration {
 146     scan_on_push = true
 147   }
 148 }
 149 
 150 # --- IAM ROLES FOR ECS ---
 151 resource "aws_iam_role" "ecs_execution_role" {
 152   name = "cherrypy-ecs-execution-role"
 153 
 154   assume_role_policy = jsonencode({
 155     Version = "2012-10-17"
 156     Statement = [{
 157       Action = "sts:AssumeRole"
 158       Effect = "Allow"
 159       Principal = { Service = "ecs-tasks.amazonaws.com" }
 160     }]
 161   })
 162 }
 163 
 164 resource "aws_iam_role_policy_attachment" "ecs_execution" {
 165   role       = aws_iam_role.ecs_execution_role.name
 166   policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
 167 }
 168 
 169 # --- CONTAINER ORCHESTRATION (ECS) ---
 170 resource "aws_ecs_cluster" "main" {
 171   name = "cherrypy-ecs-cluster"
 172 }
 173 
 174 resource "aws_ecs_task_definition" "app" {
 175   family                   = "cherrypy-task"
 176   network_mode             = "awsvpc"
 177   requires_compatibilities = ["FARGATE"]
 178   cpu                      = "256"
 179   memory                   = "512"
 180   execution_role_arn       = aws_iam_role.ecs_execution_role.arn
 181 
 182   container_definitions = jsonencode([{
 183     name      = "cherrypy-container"
 184     image     = "${aws_ecr_repository.app.repository_url}:latest"
 185     essential = true
 186     portMappings = [{
 187       containerPort = 8080
 188       hostPort      = 8080
 189     }]
 190     environment = [
 191       { name = "DB_HOST", value = aws_db_instance.postgres.address },
 192       { name = "DB_NAME", value = aws_db_instance.postgres.db_name },
 193       { name = "DB_USER", value = aws_db_instance.postgres.username },
 194       { name = "DB_PASS", value = var.db_password }
 195     ]
 196   }])
 197 }
 198 
 199 resource "aws_ecs_service" "main" {
 200   name            = "cherrypy-service"
 201   cluster         = aws_ecs_cluster.main.id
 202   task_definition = aws_ecs_task_definition.app.arn
 203   desired_count   = 1
 204   launch_type     = "FARGATE"
 205 
 206   network_configuration {
 207     subnets          = [aws_subnet.public_1.id, aws_subnet.public_2.id]
 208     security_groups  = [aws_security_group.ecs_tasks.id]
 209     assign_public_ip = true
 210   }
 211 }
 212 
 213 # --- OUTPUTS ---
 214 output "ecr_repository_url" {
 215   value       = aws_ecr_repository.app.repository_url
 216   description = "Use URL to push the CherryPy Docker image"
 217 }
 218 
 219 output "db_endpoint" {
 220   value       = aws_db_instance.postgres.endpoint
 221   description = "PostgreSQL database endpoint"
 222 }

   1 sudo apk add aws-cli --repository=https://alpinelinux.org
   2 aws --version
   3 aws configure
   4 sudo apk add terraform --repository=https://alpinelinux.org
   5 terraform --version
   6 terraform init
   7 terraform validate 
   8 terraform plan 
   9 terraform applu 
  10 aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ://amazonaws.com
  11 docker build -t cherrypy-app .
  12 docker tag cherrypy-app:latest ://amazonaws.com
  13 docker push ://amazonaws.com

terraform (last edited 2026-08-23 15:15:03 by vitor)