SkyForge

Terraform / IaC

SkyForge

Terraform for AWS: a single-EC2 Docker host, or ECS Fargate, tested offline.

Reference architecture and offline tests for a fictional Initech checkout-api. This is not a client project. No AWS account is used and nothing here is a price.

构建时记录 . Recorded when this demo was built. The page does not re-run Terraform, tflint, or checkov.

Two routes

A. Single EC2
Clients Elastic IP EC2 t3, Amazon Linux 2023 nginx checkout-api Docker Compose from user_data Security group 80 and 443 EBS gp3, encrypted DLM daily snapshot SSM Session Manager no port 22 CloudWatch agent logs
B. ECS Fargate
Clients ALB + ACM 443 forward 80 redirect VPC, two availability zones Public subnet one NAT optional off Private AZ a Fargate task Private AZ b Fargate task ECR SSM parameter Optional RDS Postgres private subnets only CloudWatch Logs CPU autoscaling

Resource inventory

Counted with python-hcl2 from the .tf files. A module file counts once, not once per caller.

79 resource blocks, 7 module blocks.

Resource typeCount
aws_appautoscaling_policy1
aws_appautoscaling_target1
aws_cloudwatch_log_group3
aws_db_instance1
aws_db_subnet_group1
aws_default_security_group1
aws_dlm_lifecycle_policy1
aws_dynamodb_table1
aws_ecr_lifecycle_policy1
aws_ecr_repository1
aws_ecs_cluster1
aws_ecs_service1
aws_ecs_task_definition1
aws_eip2
aws_flow_log1
aws_iam_instance_profile1
aws_iam_role6
aws_iam_role_policy6
aws_iam_role_policy_attachment1
aws_instance1
aws_internet_gateway1
aws_kms_alias3
aws_kms_key3
aws_lb1
aws_lb_listener2
aws_lb_target_group1
aws_nat_gateway1
aws_route2
aws_route_table2
aws_route_table_association2
aws_s3_bucket2
aws_s3_bucket_lifecycle_configuration2
aws_s3_bucket_ownership_controls2
aws_s3_bucket_policy2
aws_s3_bucket_public_access_block2
aws_s3_bucket_server_side_encryption_configuration2
aws_s3_bucket_versioning2
aws_security_group5
aws_security_group_rule1
aws_ssm_parameter3
aws_subnet2
aws_vpc1
aws_vpc_endpoint2

Module calls

ModuleSourceFile
state../modules/state_backendbootstrap/main.tf
network../modules/networkecs-fargate/main.tf
ecr../modules/ecrecs-fargate/main.tf
alb../modules/albecs-fargate/main.tf
service../modules/ecs_serviceecs-fargate/main.tf
network../modules/networksingle-ec2/main.tf
host../modules/ec2_docker_hostsingle-ec2/main.tf

Checks

构建时记录. These cards repeat data/checks.json. Opening the page does not run Terraform.

terraform: Terraform v1.9.8 · tflint: TFLint version 0.55.1 · checkov: 3.2.449

AWS_ACCESS_KEY_ID was empty when these commands ran.

terraform fmt -check

pass

exit 0

Started 2026-10-03T06:25:29Z · 0.181s

terraform validate

pass

3 roots

  • single_ec2 exit 0 · 2.04s
  • ecs_fargate exit 0 · 2.431s
  • bootstrap exit 0 · 2.007s

tflint

pass

exit 0

Started 2026-10-03T06:25:35Z · 1.058s

checkov

fail

14 failed

passed 218, failed 14, skipped 2. HIGH failures: 0.

checkov 3.2.449 leaves severity empty unless a Bridgecrew or Prisma API key is set. Each severity below is the Severity field on that check's public Prisma Cloud policy page, fetched while this file was written.

Failed checks

  • CKV_AWS_260 LOW · module.alb.aws_security_group.alb · Ensure no security groups allow ingress from 0.0.0.0:0 to port 80
  • CKV_AWS_150 LOW · module.alb.aws_lb.this · Ensure that Load Balancer has deletion protection enabled
  • CKV_AWS_260 LOW · module.host.aws_security_group.host · Ensure no security groups allow ingress from 0.0.0.0:0 to port 80
  • CKV2_AWS_62 LOW · module.alb.aws_s3_bucket.logs · Ensure S3 buckets should have event notifications enabled
  • CKV2_AWS_62 LOW · module.state.aws_s3_bucket.state · Ensure S3 buckets should have event notifications enabled
  • CKV2_AWS_19 LOW · module.network.aws_eip.nat[0] · Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances
  • CKV_AWS_144 LOW · module.alb.aws_s3_bucket.logs · Ensure that S3 bucket has cross-region replication enabled
  • CKV_AWS_144 LOW · module.state.aws_s3_bucket.state · Ensure that S3 bucket has cross-region replication enabled
  • CKV2_AWS_5 LOW · module.network.aws_security_group.endpoints[0] · Ensure that Security Groups are attached to another resource
  • CKV2_AWS_34 LOW · module.host.aws_ssm_parameter.cwagent · AWS SSM Parameter should be Encrypted
  • CKV2_AWS_30 LOW · aws_db_instance.app · Ensure Postgres RDS as aws_db_instance has Query Logging enabled
  • CKV_AWS_18 LOW · module.alb.aws_s3_bucket.logs · Ensure the S3 bucket has access logging enabled
  • CKV_AWS_18 LOW · module.state.aws_s3_bucket.state · Ensure the S3 bucket has access logging enabled
  • CKV2_AWS_28 LOW · module.alb.aws_lb.this · Ensure public facing ALB are protected by WAF

HIGH exceptions

  • CKV_AWS_355 HIGH · module.host.aws_iam_role_policy.session — ssmmessages, ec2messages, and ssm:UpdateInstanceInformation do not support resource-level permissions; AWS requires Resource *
  • CKV_AWS_290 HIGH · module.host.aws_iam_role_policy.session — those Session Manager write actions cannot be constrained to a resource ARN

terraform test

pass

6 runs

Started 2026-10-03T06:25:41Z · 5.071s. passed 6, failed 0, errored 0, skipped 0.

  • ec2_dev_sg_and_tags pass
  • ec2_prod_size pass
  • ecs_dev_alb_role_and_tags pass
  • ecs_prod_size pass
  • ecs_without_nat pass
  • ecs_with_rds pass

Which route

No prices. Sizes are read from envs/dev.tfvars and envs/prod.tfvars.

Single EC2ECS Fargate
ShapeOne host, Compose, user_dataVPC, ALB, ECR, Fargate service
AvailabilityOne machine in one zoneTwo zones, load balancer, rolling deploy
OperationsPatch the OS. SSM instead of SSH. Daily EBS snapshots.No host OS. Ship a new task definition.
Change in sizeReplace the instanceCPU target tracking between min and max
Dev in the examplet3.small256 CPU / 512 MiB
Prod in the examplet3.medium512 CPU / 1024 MiB
Fits whenThe service can live on one machineA task or a zone failing should not take the service down

Files

ecs-fargate/variables.tf

variable "project" {
  type        = string
  description = "Project tag. The example is checkout-api for the fictional Initech service."
  default     = "checkout-api"
}

variable "env" {
  type        = string
  description = "Environment tag. dev and prod use different Fargate sizes."
  default     = "dev"
}

variable "owner" {
  type        = string
  description = "Owner tag. Initech is fictional. This is not a client account."
  default     = "initech-platform"
}

variable "region" {
  type        = string
  description = "AWS region."
  default     = "us-east-1"
}

variable "instance_type" {
  type        = string
  description = "EC2 instance type from the shared tfvars file. This route does not launch an instance."
  default     = "t3.small"
}

variable "task_cpu" {
  type        = number
  description = "Fargate CPU units."
  default     = 256
}

variable "task_memory" {
  type        = number
  description = "Fargate memory in MiB."
  default     = 512
}

variable "desired_count" {
  type        = number
  description = "Starting number of tasks."
  default     = 1
}

variable "min_capacity" {
  type        = number
  description = "Minimum tasks for CPU target tracking."
  default     = 1
}

variable "max_capacity" {
  type        = number
  description = "Maximum tasks for CPU target tracking."
  default     = 2
}

variable "cpu_target" {
  type        = number
  description = "Target average CPU percent."
  default     = 60
}

variable "enable_nat" {
  type        = bool
  description = "Create one NAT gateway. When false, private tasks use interface endpoints."
  default     = true
}

variable "enable_rds" {
  type        = bool
  description = "Create an optional Postgres instance in the private subnets."
  default     = false
}

variable "certificate_arn" {
  type        = string
  description = "ACM certificate ARN for the HTTPS listener. The operator passes this in."
  default     = "arn:aws:acm:us-east-1:111111111111:certificate/11111111-1111-1111-1111-111111111111"

  validation {
    condition     = can(regex("^arn:aws[a-zA-Z-]*:acm:[a-z0-9-]+:[0-9]{12}:certificate/.+$", var.certificate_arn))
    error_message = "certificate_arn must be an ACM certificate ARN."
  }
}

variable "alb_log_bucket" {
  type        = string
  description = "Access-log bucket name. Replace the example before any real plan."
  default     = "initech-checkout-api-dev-alb-logs-example"
}

variable "container_image" {
  type        = string
  description = "Image URI including the tag. Replace the placeholder after scripts/push_ecr.sh."
  default     = "replace-account.dkr.ecr.us-east-1.amazonaws.com/checkout-api:example"
}

variable "app_secret" {
  type        = string
  description = "Placeholder application secret written to SSM. Replace it before any real plan. The demo never applies this."
  default     = "replace-before-apply"
  sensitive   = true
}

variable "availability_zones" {
  type        = list(string)
  description = "Optional AZ override. Empty uses the first two zones from the available-zones data source."
  default     = []
}