mysystem-cli 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/AGENTS.md +66 -0
  2. package/copy-templates.js +47 -0
  3. package/dist/commands/init.js +15 -2
  4. package/dist/utils/detector.js +84 -32
  5. package/package.json +2 -2
  6. package/src/commands/init.ts +15 -2
  7. package/src/utils/detector.ts +86 -29
  8. package/templates/docker/fastapi.Dockerfile +32 -0
  9. package/templates/docker/nextjs.Dockerfile +45 -0
  10. package/templates/docker/node.Dockerfile +32 -0
  11. package/templates/docker/react.Dockerfile +38 -0
  12. package/templates/github/deploy-ec2.yml +163 -0
  13. package/templates/github/deploy.yml +94 -0
  14. package/templates/github/destroy.yml +43 -0
  15. package/templates/terraform/alb.tf +69 -0
  16. package/templates/terraform/bootstrap-oidc.yaml +69 -0
  17. package/templates/terraform/budget.tf +40 -0
  18. package/templates/terraform/db.tf +46 -0
  19. package/templates/terraform/dns.tf +89 -0
  20. package/templates/terraform/ecs.tf +110 -0
  21. package/templates/terraform/outputs.tf +14 -0
  22. package/templates/terraform/provider.tf +21 -0
  23. package/templates/terraform/rds_proxy.tf +157 -0
  24. package/templates/terraform/redis.tf +69 -0
  25. package/templates/terraform/security.tf +156 -0
  26. package/templates/terraform/variables.tf +46 -0
  27. package/templates/terraform/vpc.tf +68 -0
  28. package/templates/terraform/waf.tf +97 -0
  29. package/templates/terraform-ec2/budget.tf +40 -0
  30. package/templates/terraform-ec2/dns.tf +85 -0
  31. package/templates/terraform-ec2/ec2.tf +124 -0
  32. package/templates/terraform-ec2/ecr.tf +10 -0
  33. package/templates/terraform-ec2/outputs.tf +19 -0
  34. package/templates/terraform-ec2/provider.tf +22 -0
  35. package/templates/terraform-ec2/variables.tf +58 -0
  36. package/templates/terraform-ec2/vpc.tf +50 -0
@@ -0,0 +1,110 @@
1
+ # --- Amazon ECR (Container Registry) ---
2
+ resource "aws_ecr_repository" "app" {
3
+ name = var.app_name
4
+ image_tag_mutability = "MUTABLE"
5
+ force_destroy = true
6
+
7
+ image_scanning_configuration {
8
+ scan_on_push = true
9
+ }
10
+ }
11
+
12
+ # --- ECS Cluster ---
13
+ resource "aws_ecs_cluster" "main" {
14
+ name = "${var.app_name}-cluster"
15
+ }
16
+
17
+ # --- CloudWatch Logs ---
18
+ resource "aws_cloudwatch_log_group" "ecs" {
19
+ name = "/ecs/${var.app_name}"
20
+ retention_in_days = 7
21
+ }
22
+
23
+ # --- ECS Task Definition ---
24
+ resource "aws_ecs_task_definition" "app" {
25
+ family = var.app_name
26
+ network_mode = "awsvpc"
27
+ requires_compatibilities = ["FARGATE"]
28
+ cpu = var.container_cpu
29
+ memory = var.container_memory
30
+ execution_role_arn = aws_iam_role.ecs_execution.arn
31
+ task_role_arn = aws_iam_role.ecs_task.arn
32
+
33
+ container_definitions = jsonencode([
34
+ {
35
+ name = var.app_name
36
+ # We use a placeholder image for the initial provision, so terraform succeeds.
37
+ # The CI/CD pipeline will overwrite this image during actual deployment.
38
+ image = "${aws_ecr_repository.app.repository_url}:latest"
39
+ essential = true
40
+ portMappings = [
41
+ {
42
+ containerPort = var.container_port
43
+ hostPort = var.container_port
44
+ }
45
+ ]
46
+ logConfiguration = {
47
+ logDriver = "awslogs"
48
+ options = {
49
+ "awslogs-group" = aws_cloudwatch_log_group.ecs.name
50
+ "awslogs-region" = var.aws_region
51
+ "awslogs-stream-prefix" = "ecs"
52
+ }
53
+ }
54
+ environment = concat(
55
+ [
56
+ {
57
+ name = "PORT"
58
+ value = tostring(var.container_port)
59
+ },
60
+ {
61
+ name = "NODE_ENV"
62
+ value = var.environment
63
+ }
64
+ ],
65
+ var.enable_redis ? [
66
+ {
67
+ name = "REDIS_URL"
68
+ value = "redis://${aws_elasticache_replication_group.redis[0].primary_endpoint_address}:6379"
69
+ }
70
+ ] : [],
71
+ var.sentry_dsn != "" ? [
72
+ {
73
+ name = "SENTRY_DSN"
74
+ value = var.sentry_dsn
75
+ }
76
+ ] : []
77
+ )
78
+ # Securely load database URL parameter if database is enabled
79
+ secrets = var.enable_database ? [
80
+ {
81
+ name = "DATABASE_URL"
82
+ valueFrom = aws_ssm_parameter.db_url[0].arn
83
+ }
84
+ ] : []
85
+ }
86
+ ])
87
+ }
88
+
89
+ # --- ECS Service ---
90
+ resource "aws_ecs_service" "main" {
91
+ name = var.app_name
92
+ cluster = aws_ecs_cluster.main.id
93
+ task_definition = aws_ecs_task_definition.app.arn
94
+ desired_count = 1
95
+ launch_type = "FARGATE"
96
+
97
+ network_configuration {
98
+ subnets = aws_subnet.public[*].id # Required for pulling ECR images without NAT Gateway
99
+ security_groups = [aws_security_group.ecs.id]
100
+ assign_public_ip = true
101
+ }
102
+
103
+ load_balancer {
104
+ target_group_arn = aws_lb_target_group.app.arn
105
+ container_name = var.app_name
106
+ container_port = var.container_port
107
+ }
108
+
109
+ depends_on = [aws_lb_listener.http]
110
+ }
@@ -0,0 +1,14 @@
1
+ output "ecr_repository_url" {
2
+ value = aws_ecr_repository.app.repository_url
3
+ description = "URL of the ECR repository"
4
+ }
5
+
6
+ output "application_url" {
7
+ value = "http://${aws_lb.main.dns_name}"
8
+ description = "Public URL of the Application Load Balancer"
9
+ }
10
+
11
+ output "database_endpoint" {
12
+ value = var.enable_database ? aws_db_instance.postgres[0].endpoint : "None"
13
+ description = "RDS database endpoint"
14
+ }
@@ -0,0 +1,21 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
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
+ default_tags {
15
+ tags = {
16
+ Environment = var.environment
17
+ ManagedBy = "MySystem"
18
+ Project = var.app_name
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,157 @@
1
+ # AWS RDS Proxy (Managed PgBouncer Connection Pooler)
2
+ # Pools database connections to allow the application to scale to thousands of concurrent users
3
+ # without exhausting database connection limits or memory.
4
+
5
+ variable "enable_rds_proxy" {
6
+ type = bool
7
+ description = "Whether to provision an RDS Proxy connection pooler"
8
+ default = false
9
+ }
10
+
11
+ # 1. AWS Secrets Manager Secret to store RDS credentials (required for RDS Proxy)
12
+ resource "aws_secretsmanager_secret" "db_credentials" {
13
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
14
+ name = "${var.app_name}-db-credentials"
15
+ recovery_window_in_days = 0 # force deletion on destroy for clean cleanup
16
+ }
17
+
18
+ resource "aws_secretsmanager_secret_version" "db_credentials" {
19
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
20
+ secret_id = aws_secretsmanager_secret.db_credentials[0].id
21
+ secret_string = jsonencode({
22
+ username = aws_db_instance.postgres[0].username
23
+ password = random_password.db_password[0].result
24
+ engine = "postgres"
25
+ host = aws_db_instance.postgres[0].address
26
+ port = 5432
27
+ dbInstanceIdentifier = aws_db_instance.postgres[0].identifier
28
+ })
29
+ }
30
+
31
+ # 2. IAM Role for RDS Proxy to access Secrets Manager
32
+ resource "aws_iam_role" "rds_proxy" {
33
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
34
+ name = "${var.app_name}-rds-proxy-role"
35
+
36
+ assume_role_policy = jsonencode({
37
+ Version = "2012-10-17"
38
+ Statement = [
39
+ {
40
+ Action = "sts:AssumeRole"
41
+ Effect = "Allow"
42
+ Principal = {
43
+ Service = "rds.amazonaws.com"
44
+ }
45
+ }
46
+ ]
47
+ })
48
+ }
49
+
50
+ resource "aws_iam_policy" "rds_proxy" {
51
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
52
+ name = "${var.app_name}-rds-proxy-policy"
53
+ description = "Allows RDS Proxy to read secrets from Secrets Manager"
54
+
55
+ policy = jsonencode({
56
+ Version = "2012-10-17"
57
+ Statement = [
58
+ {
59
+ Action = [
60
+ "secretsmanager:GetSecretValue",
61
+ "secretsmanager:DescribeSecret"
62
+ ]
63
+ Effect = "Allow"
64
+ Resource = [aws_secretsmanager_secret.db_credentials[0].arn]
65
+ }
66
+ ]
67
+ })
68
+ }
69
+
70
+ resource "aws_iam_role_policy_attachment" "rds_proxy" {
71
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
72
+ role = aws_iam_role.rds_proxy[0].name
73
+ policy_arn = aws_iam_policy.rds_proxy[0].arn
74
+ }
75
+
76
+ # 3. RDS Proxy Security Group
77
+ resource "aws_security_group" "rds_proxy" {
78
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
79
+ name = "${var.app_name}-rds-proxy-sg"
80
+ description = "Security Group for RDS Proxy"
81
+ vpc_id = aws_vpc.main.id
82
+
83
+ # Allow ECS tasks to connect to the RDS Proxy
84
+ ingress {
85
+ description = "Allow DB traffic from ECS tasks"
86
+ from_port = 5432
87
+ to_port = 5432
88
+ protocol = "tcp"
89
+ security_groups = [aws_security_group.ecs.id]
90
+ }
91
+
92
+ egress {
93
+ from_port = 0
94
+ to_port = 0
95
+ protocol = "-1"
96
+ cidr_blocks = ["0.0.0.0/0"]
97
+ }
98
+
99
+ tags = {
100
+ Name = "${var.app_name}-rds-proxy-sg"
101
+ }
102
+ }
103
+
104
+ # Allow RDS database to accept connections from the RDS Proxy
105
+ resource "aws_security_group_rule" "db_allow_rds_proxy" {
106
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
107
+ type = "ingress"
108
+ from_port = 5432
109
+ to_port = 5432
110
+ protocol = "tcp"
111
+ security_group_id = aws_security_group.db[0].id
112
+ source_security_group_id = aws_security_group.rds_proxy[0].id
113
+ description = "Allow traffic from RDS Proxy"
114
+ }
115
+
116
+ # 4. RDS Proxy
117
+ resource "aws_db_proxy" "postgres" {
118
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
119
+ name = "${var.app_name}-db-proxy"
120
+ debug_logging = false
121
+ engine_family = "POSTGRESQL"
122
+ idle_client_timeout = 1800
123
+ require_tls = true
124
+ role_arn = aws_iam_role.rds_proxy[0].arn
125
+ vpc_security_group_ids = [aws_security_group.rds_proxy[0].id]
126
+ vpc_subnet_ids = aws_subnet.private[*].id
127
+
128
+ auth {
129
+ auth_scheme = "SECRETS"
130
+ description = "Database authentication via Secrets Manager"
131
+ iam_auth = "DISABLED"
132
+ secret_arn = aws_secretsmanager_secret.db_credentials[0].arn
133
+ }
134
+
135
+ tags = {
136
+ Name = "${var.app_name}-db-proxy"
137
+ }
138
+ }
139
+
140
+ # 5. Connect RDS Proxy to the database instance
141
+ resource "aws_db_proxy_default_target_group" "postgres" {
142
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
143
+ db_proxy_name = aws_db_proxy.postgres[0].name
144
+
145
+ connection_pool_config {
146
+ max_connections_percent = 100
147
+ max_idle_connections_percent = 50
148
+ connection_borrow_timeout = 120
149
+ }
150
+ }
151
+
152
+ resource "aws_db_proxy_target" "postgres" {
153
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
154
+ db_proxy_name = aws_db_proxy.postgres[0].name
155
+ target_group_name = aws_db_proxy_default_target_group.postgres[0].name
156
+ db_instance_identifier = aws_db_instance.postgres[0].id
157
+ }
@@ -0,0 +1,69 @@
1
+ # Amazon ElastiCache Redis Cluster
2
+ # Provides high-performance sub-millisecond caching and session/queue management.
3
+
4
+ variable "enable_redis" {
5
+ type = bool
6
+ description = "Whether to provision an ElastiCache Redis cluster"
7
+ default = false
8
+ }
9
+
10
+ # Redis Security Group
11
+ resource "aws_security_group" "redis" {
12
+ count = var.enable_redis ? 1 : 0
13
+ name = "${var.app_name}-redis-sg"
14
+ description = "Access to Redis from ECS tasks only"
15
+ vpc_id = aws_vpc.main.id
16
+
17
+ ingress {
18
+ description = "Allow Redis access from ECS tasks"
19
+ from_port = 6379
20
+ to_port = 6379
21
+ protocol = "tcp"
22
+ security_groups = [aws_security_group.ecs.id]
23
+ }
24
+
25
+ egress {
26
+ from_port = 0
27
+ to_port = 0
28
+ protocol = "-1"
29
+ cidr_blocks = ["0.0.0.0/0"]
30
+ }
31
+
32
+ tags = {
33
+ Name = "${var.app_name}-redis-sg"
34
+ }
35
+ }
36
+
37
+ # Redis Subnet Group (places Redis in private subnets)
38
+ resource "aws_elasticache_subnet_group" "redis" {
39
+ count = var.enable_redis ? 1 : 0
40
+ name = "${var.app_name}-redis-subnet-group"
41
+ subnet_ids = aws_subnet.private[*].id
42
+ }
43
+
44
+ # Redis Replication Group (Cluster)
45
+ resource "aws_elasticache_replication_group" "redis" {
46
+ count = var.enable_redis ? 1 : 0
47
+ replication_group_id = "${var.app_name}-redis"
48
+ description = "Redis cluster for ${var.app_name}"
49
+ node_type = "cache.t4g.micro" # cost-effective Graviton node
50
+ num_cache_clusters = 1
51
+ parameter_group_name = "default.redis7"
52
+ port = 6379
53
+ subnet_group_name = aws_elasticache_subnet_group.redis[0].name
54
+ security_group_ids = [aws_security_group.redis[0].id]
55
+ automatic_failover_enabled = false # Disable for single node to save cost
56
+
57
+ tags = {
58
+ Name = "${var.app_name}-redis"
59
+ }
60
+ }
61
+
62
+ # Save Redis URL in SSM Parameter Store
63
+ resource "aws_ssm_parameter" "redis_url" {
64
+ count = var.enable_redis ? 1 : 0
65
+ name = "/${var.app_name}/redis_url"
66
+ type = "SecureString"
67
+ description = "Redis URL for the application"
68
+ value = "redis://${aws_elasticache_replication_group.redis[0].primary_endpoint_address}:6379"
69
+ }
@@ -0,0 +1,156 @@
1
+ # --- Security Groups ---
2
+
3
+ # ALB Security Group
4
+ resource "aws_security_group" "alb" {
5
+ name = "${var.app_name}-alb-sg"
6
+ description = "Allow inbound HTTP/HTTPS traffic to ALB"
7
+ vpc_id = aws_vpc.main.id
8
+
9
+ ingress {
10
+ description = "Allow HTTP"
11
+ from_port = 80
12
+ to_port = 80
13
+ protocol = "tcp"
14
+ cidr_blocks = ["0.0.0.0/0"]
15
+ }
16
+
17
+ ingress {
18
+ description = "Allow HTTPS"
19
+ from_port = 443
20
+ to_port = 443
21
+ protocol = "tcp"
22
+ cidr_blocks = ["0.0.0.0/0"]
23
+ }
24
+
25
+ egress {
26
+ from_port = 0
27
+ to_port = 0
28
+ protocol = "-1"
29
+ cidr_blocks = ["0.0.0.0/0"]
30
+ }
31
+
32
+ tags = {
33
+ Name = "${var.app_name}-alb-sg"
34
+ }
35
+ }
36
+
37
+ # ECS Tasks Security Group
38
+ resource "aws_security_group" "ecs" {
39
+ name = "${var.app_name}-ecs-tasks-sg"
40
+ description = "Access to ECS tasks only from ALB"
41
+ vpc_id = aws_vpc.main.id
42
+
43
+ ingress {
44
+ description = "Allow traffic from ALB only"
45
+ from_port = var.container_port
46
+ to_port = var.container_port
47
+ protocol = "tcp"
48
+ security_groups = [aws_security_group.alb.id]
49
+ }
50
+
51
+ egress {
52
+ from_port = 0
53
+ to_port = 0
54
+ protocol = "-1"
55
+ cidr_blocks = ["0.0.0.0/0"]
56
+ }
57
+
58
+ tags = {
59
+ Name = "${var.app_name}-ecs-tasks-sg"
60
+ }
61
+ }
62
+
63
+ # RDS Security Group
64
+ resource "aws_security_group" "db" {
65
+ count = var.enable_database ? 1 : 0
66
+ name = "${var.app_name}-db-sg"
67
+ description = "Access to RDS from ECS tasks only"
68
+ vpc_id = aws_vpc.main.id
69
+
70
+ ingress {
71
+ description = "Allow PostgreSQL access from ECS tasks"
72
+ from_port = 5432
73
+ to_port = 5432
74
+ protocol = "tcp"
75
+ security_groups = [aws_security_group.ecs.id]
76
+ }
77
+
78
+ egress {
79
+ from_port = 0
80
+ to_port = 0
81
+ protocol = "-1"
82
+ cidr_blocks = ["0.0.0.0/0"]
83
+ }
84
+
85
+ tags = {
86
+ Name = "${var.app_name}-db-sg"
87
+ }
88
+ }
89
+
90
+ # --- IAM Roles ---
91
+
92
+ # ECS Task Execution Role (Required to pull ECR images & push logs to CloudWatch)
93
+ resource "aws_iam_role" "ecs_execution" {
94
+ name = "${var.app_name}-ecs-execution-role"
95
+
96
+ assume_role_policy = jsonencode({
97
+ Version = "2012-10-17"
98
+ Statement = [
99
+ {
100
+ Action = "sts:AssumeRole"
101
+ Effect = "Allow"
102
+ Principal = {
103
+ Service = "ecs-tasks.amazonaws.com"
104
+ }
105
+ }
106
+ ]
107
+ })
108
+ }
109
+
110
+ resource "aws_iam_role_policy_attachment" "ecs_execution" {
111
+ role = aws_iam_role.ecs_execution.name
112
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
113
+ }
114
+
115
+ # ECS Task Role (Permissions for the application itself)
116
+ resource "aws_iam_role" "ecs_task" {
117
+ name = "${var.app_name}-ecs-task-role"
118
+
119
+ assume_role_policy = jsonencode({
120
+ Version = "2012-10-17"
121
+ Statement = [
122
+ {
123
+ Action = "sts:AssumeRole"
124
+ Effect = "Allow"
125
+ Principal = {
126
+ Service = "ecs-tasks.amazonaws.com"
127
+ }
128
+ }
129
+ ]
130
+ })
131
+ }
132
+
133
+ # Add standard policy for ECS task (e.g. basic S3 or secrets reading if needed, can be expanded)
134
+ resource "aws_iam_policy" "ecs_task_policy" {
135
+ name = "${var.app_name}-ecs-task-policy"
136
+ description = "Basic policy for ECS task execution"
137
+
138
+ policy = jsonencode({
139
+ Version = "2012-10-17"
140
+ Statement = [
141
+ {
142
+ Action = [
143
+ "ssm:GetParameters",
144
+ "secretsmanager:GetSecretValue"
145
+ ]
146
+ Effect = "Allow"
147
+ Resource = "*"
148
+ }
149
+ ]
150
+ })
151
+ }
152
+
153
+ resource "aws_iam_role_policy_attachment" "ecs_task" {
154
+ role = aws_iam_role.ecs_task.name
155
+ policy_arn = aws_iam_policy.ecs_task_policy.arn
156
+ }
@@ -0,0 +1,46 @@
1
+ variable "aws_region" {
2
+ type = string
3
+ description = "AWS region to deploy resources"
4
+ default = "us-east-1"
5
+ }
6
+
7
+ variable "app_name" {
8
+ type = string
9
+ description = "Application name (used for naming resources)"
10
+ }
11
+
12
+ variable "environment" {
13
+ type = string
14
+ description = "Deployment environment"
15
+ default = "production"
16
+ }
17
+
18
+ variable "container_port" {
19
+ type = number
20
+ description = "Port the application container listens on"
21
+ default = 3000
22
+ }
23
+
24
+ variable "container_cpu" {
25
+ type = number
26
+ description = "CPU units for the ECS task (1024 = 1 vCPU)"
27
+ default = 256
28
+ }
29
+
30
+ variable "container_memory" {
31
+ type = number
32
+ description = "Memory for the ECS task in MB"
33
+ default = 512
34
+ }
35
+
36
+ variable "enable_database" {
37
+ type = bool
38
+ description = "Whether to provision an RDS PostgreSQL database"
39
+ default = true
40
+ }
41
+
42
+ variable "sentry_dsn" {
43
+ type = string
44
+ description = "Sentry Data Source Name (DSN) for error tracking"
45
+ default = ""
46
+ }
@@ -0,0 +1,68 @@
1
+ data "aws_availability_zones" "available" {
2
+ state = "available"
3
+ }
4
+
5
+ resource "aws_vpc" "main" {
6
+ cidr_block = "10.0.0.0/16"
7
+ enable_dns_hostnames = true
8
+ enable_dns_support = true
9
+
10
+ tags = {
11
+ Name = "${var.app_name}-vpc"
12
+ }
13
+ }
14
+
15
+ resource "aws_subnet" "public" {
16
+ count = 2
17
+ vpc_id = aws_vpc.main.id
18
+ cidr_block = "10.0.${count.index}.0/24"
19
+ availability_zone = data.aws_availability_zones.available.names[count.index]
20
+ map_public_ip_on_launch = true
21
+
22
+ tags = {
23
+ Name = "${var.app_name}-public-subnet-${count.index}"
24
+ }
25
+ }
26
+
27
+ resource "aws_subnet" "private" {
28
+ count = 2
29
+ vpc_id = aws_vpc.main.id
30
+ cidr_block = "10.0.${count.index + 10}.0/24"
31
+ availability_zone = data.aws_availability_zones.available.names[count.index]
32
+
33
+ tags = {
34
+ Name = "${var.app_name}-private-subnet-${count.index}"
35
+ }
36
+ }
37
+
38
+ resource "aws_internet_gateway" "gw" {
39
+ vpc_id = aws_vpc.main.id
40
+
41
+ tags = {
42
+ Name = "${var.app_name}-igw"
43
+ }
44
+ }
45
+
46
+ resource "aws_route_table" "public" {
47
+ vpc_id = aws_vpc.main.id
48
+
49
+ route {
50
+ cidr_block = "0.0.0.0/0"
51
+ gateway_id = aws_internet_gateway.gw.id
52
+ }
53
+
54
+ tags = {
55
+ Name = "${var.app_name}-public-rt"
56
+ }
57
+ }
58
+
59
+ resource "aws_route_table_association" "public" {
60
+ count = 2
61
+ subnet_id = aws_subnet.public[count.index].id
62
+ route_table_id = aws_route_table.public.id
63
+ }
64
+
65
+ # Note: We do not provision a NAT Gateway by default to save ~$32/month in costs.
66
+ # Fargate tasks run in the public subnets but are secured via Security Groups
67
+ # allowing traffic ONLY from the Application Load Balancer (ALB).
68
+ # This provides enterprise-level routing security at near-zero baseline network cost.