terraform fmt -check
passStarted 2026-10-03T06:25:29Z · 0.181s
Terraform / IaC
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.
Counted with python-hcl2 from the .tf files. A module file counts once, not once per caller.
79 resource blocks, 7 module blocks.
| Resource type | Count |
|---|---|
aws_appautoscaling_policy | 1 |
aws_appautoscaling_target | 1 |
aws_cloudwatch_log_group | 3 |
aws_db_instance | 1 |
aws_db_subnet_group | 1 |
aws_default_security_group | 1 |
aws_dlm_lifecycle_policy | 1 |
aws_dynamodb_table | 1 |
aws_ecr_lifecycle_policy | 1 |
aws_ecr_repository | 1 |
aws_ecs_cluster | 1 |
aws_ecs_service | 1 |
aws_ecs_task_definition | 1 |
aws_eip | 2 |
aws_flow_log | 1 |
aws_iam_instance_profile | 1 |
aws_iam_role | 6 |
aws_iam_role_policy | 6 |
aws_iam_role_policy_attachment | 1 |
aws_instance | 1 |
aws_internet_gateway | 1 |
aws_kms_alias | 3 |
aws_kms_key | 3 |
aws_lb | 1 |
aws_lb_listener | 2 |
aws_lb_target_group | 1 |
aws_nat_gateway | 1 |
aws_route | 2 |
aws_route_table | 2 |
aws_route_table_association | 2 |
aws_s3_bucket | 2 |
aws_s3_bucket_lifecycle_configuration | 2 |
aws_s3_bucket_ownership_controls | 2 |
aws_s3_bucket_policy | 2 |
aws_s3_bucket_public_access_block | 2 |
aws_s3_bucket_server_side_encryption_configuration | 2 |
aws_s3_bucket_versioning | 2 |
aws_security_group | 5 |
aws_security_group_rule | 1 |
aws_ssm_parameter | 3 |
aws_subnet | 2 |
aws_vpc | 1 |
aws_vpc_endpoint | 2 |
| Module | Source | File |
|---|---|---|
state | ../modules/state_backend | bootstrap/main.tf |
network | ../modules/network | ecs-fargate/main.tf |
ecr | ../modules/ecr | ecs-fargate/main.tf |
alb | ../modules/alb | ecs-fargate/main.tf |
service | ../modules/ecs_service | ecs-fargate/main.tf |
network | ../modules/network | single-ec2/main.tf |
host | ../modules/ec2_docker_host | single-ec2/main.tf |
构建时记录. 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.
Started 2026-10-03T06:25:29Z · 0.181s
single_ec2 exit 0 · 2.04secs_fargate exit 0 · 2.431sbootstrap exit 0 · 2.007sStarted 2026-10-03T06:25:35Z · 1.058s
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.
CKV_AWS_260 LOW · module.alb.aws_security_group.alb · Ensure no security groups allow ingress from 0.0.0.0:0 to port 80CKV_AWS_150 LOW · module.alb.aws_lb.this · Ensure that Load Balancer has deletion protection enabledCKV_AWS_260 LOW · module.host.aws_security_group.host · Ensure no security groups allow ingress from 0.0.0.0:0 to port 80CKV2_AWS_62 LOW · module.alb.aws_s3_bucket.logs · Ensure S3 buckets should have event notifications enabledCKV2_AWS_62 LOW · module.state.aws_s3_bucket.state · Ensure S3 buckets should have event notifications enabledCKV2_AWS_19 LOW · module.network.aws_eip.nat[0] · Ensure that all EIP addresses allocated to a VPC are attached to EC2 instancesCKV_AWS_144 LOW · module.alb.aws_s3_bucket.logs · Ensure that S3 bucket has cross-region replication enabledCKV_AWS_144 LOW · module.state.aws_s3_bucket.state · Ensure that S3 bucket has cross-region replication enabledCKV2_AWS_5 LOW · module.network.aws_security_group.endpoints[0] · Ensure that Security Groups are attached to another resourceCKV2_AWS_34 LOW · module.host.aws_ssm_parameter.cwagent · AWS SSM Parameter should be EncryptedCKV2_AWS_30 LOW · aws_db_instance.app · Ensure Postgres RDS as aws_db_instance has Query Logging enabledCKV_AWS_18 LOW · module.alb.aws_s3_bucket.logs · Ensure the S3 bucket has access logging enabledCKV_AWS_18 LOW · module.state.aws_s3_bucket.state · Ensure the S3 bucket has access logging enabledCKV2_AWS_28 LOW · module.alb.aws_lb.this · Ensure public facing ALB are protected by WAFCKV_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 ARNStarted 2026-10-03T06:25:41Z · 5.071s. passed 6, failed 0, errored 0, skipped 0.
ec2_dev_sg_and_tags passec2_prod_size passecs_dev_alb_role_and_tags passecs_prod_size passecs_without_nat passecs_with_rds passNo prices. Sizes are read from envs/dev.tfvars and envs/prod.tfvars.
| Single EC2 | ECS Fargate | |
|---|---|---|
| Shape | One host, Compose, user_data | VPC, ALB, ECR, Fargate service |
| Availability | One machine in one zone | Two zones, load balancer, rolling deploy |
| Operations | Patch the OS. SSM instead of SSH. Daily EBS snapshots. | No host OS. Ship a new task definition. |
| Change in size | Replace the instance | CPU target tracking between min and max |
| Dev in the example | t3.small | 256 CPU / 512 MiB |
| Prod in the example | t3.medium | 512 CPU / 1024 MiB |
| Fits when | The service can live on one machine | A task or a zone failing should not take the service down |
tests/offline.tftest.hclmock_provider "aws" {
mock_data "aws_ami" {
defaults = {
id = "ami-0mockal2023example"
architecture = "x86_64"
}
}
mock_data "aws_availability_zones" {
defaults = {
names = ["us-east-1a", "us-east-1b", "us-east-1c"]
zone_ids = ["use1-az1", "use1-az2", "use1-az3"]
}
}
mock_data "aws_caller_identity" {
defaults = {
account_id = "000000000000"
arn = "arn:aws:iam::000000000000:root"
user_id = "AIDMOCK"
}
}
}
variables {
availability_zones = ["us-east-1a", "us-east-1b"]
}
run "ec2_dev_sg_and_tags" {
command = plan
module {
source = "./single-ec2"
}
variables {
env = "dev"
instance_type = "t3.small"
}
assert {
condition = output.instance_type == "t3.small"
error_message = "dev example should plan a t3.small"
}
assert {
condition = alltrue([
for rule in output.ingress : !(rule.from_port <= 22 && rule.to_port >= 22)
])
error_message = "host security group must not open port 22"
}
assert {
condition = alltrue([
for rule in output.ingress : !(
rule.from_port <= 22 && rule.to_port >= 22 && contains(rule.cidrs, "0.0.0.0/0")
)
])
error_message = "host security group must not open port 22 to 0.0.0.0/0"
}
assert {
condition = (
length(output.resource_env_tags) > 0
&& alltrue([for tag in output.resource_env_tags : tag == "dev"])
)
error_message = "every taggable resource on the dev EC2 route must carry Env=dev"
}
}
run "ec2_prod_size" {
command = plan
module {
source = "./single-ec2"
}
variables {
env = "prod"
instance_type = "t3.medium"
}
assert {
condition = output.instance_type == "t3.medium"
error_message = "prod example should plan a t3.medium"
}
assert {
condition = output.instance_type != run.ec2_dev_sg_and_tags.instance_type
error_message = "dev and prod instance types must differ"
}
assert {
condition = (
length(output.resource_env_tags) > 0
&& alltrue([for tag in output.resource_env_tags : tag == "prod"])
)
error_message = "every taggable resource on the prod EC2 route must carry Env=prod"
}
}
run "ecs_dev_alb_role_and_tags" {
command = plan
module {
source = "./ecs-fargate"
}
variables {
env = "dev"
task_cpu = 256
task_memory = 512
enable_nat = true
enable_rds = false
}
assert {
condition = output.https_port == 443 && output.https_action == "forward"
error_message = "the load balancer must forward on 443"
}
assert {
condition = output.http_port == 80 && output.http_action == "redirect"
error_message = "port 80 must redirect and must not forward"
}
assert {
condition = alltrue([
for action in output.task_role_actions : action != "*" && !strcontains(action, "*:*")
]) && length(output.task_role_actions) > 0
error_message = "the ECS task role must not allow *:*"
}
assert {
condition = (
length(output.resource_env_tags) > 0
&& alltrue([for tag in output.resource_env_tags : tag == "dev"])
)
error_message = "every taggable resource on the dev Fargate route must carry Env=dev"
}
}
run "ecs_prod_size" {
command = plan
module {
source = "./ecs-fargate"
}
variables {
env = "prod"
task_cpu = 512
task_memory = 1024
enable_nat = true
enable_rds = false
}
assert {
condition = output.task_cpu == 512 && output.task_cpu != run.ecs_dev_alb_role_and_tags.task_cpu
error_message = "dev and prod Fargate CPU sizes must differ"
}
assert {
condition = (
length(output.resource_env_tags) > 0
&& alltrue([for tag in output.resource_env_tags : tag == "prod"])
)
error_message = "every taggable resource on the prod Fargate route must carry Env=prod"
}
}
run "ecs_without_nat" {
command = plan
module {
source = "./ecs-fargate"
}
variables {
env = "dev"
enable_nat = false
}
assert {
condition = output.https_port == 443 && output.http_action == "redirect"
error_message = "turning NAT off must keep the listener rules"
}
}
run "ecs_with_rds" {
command = plan
module {
source = "./ecs-fargate"
}
variables {
env = "dev"
enable_rds = true
}
assert {
condition = alltrue([for tag in output.resource_env_tags : tag == "dev"])
error_message = "RDS resources must carry Env=dev"
}
}