Skip to content

Resources

Rod Christiansen edited this page Sep 3, 2026 · 1 revision

Resources

Every AWS resource the module creates, grouped by the submodule that declares it. N is the number of availability zones, which is 2 unless the networking module's availability_zones variable is overridden. <prefix> is <project_name>-<environment>.

networking

Resource Count What it is for
aws_vpc.main 1 The VPC. DNS hostnames and DNS support both on, which RDS and the interface endpoints need.
aws_subnet.public N ALB and NAT gateways. map_public_ip_on_launch = true.
aws_subnet.private N ECS tasks and RDS. No public IPs.
aws_internet_gateway.main 1 Egress for the public subnets.
aws_eip.nat 1 or N Elastic IP per NAT gateway.
aws_nat_gateway.main 1 or N Outbound internet for private subnets. N only when enable_multi_az_nat.
aws_route_table.public 1 Default route to the IGW.
aws_route_table_association.public N Attaches the public subnets.
aws_route_table.private N One per AZ, so multi-AZ NAT can route each subnet through its local gateway.
aws_route_table_association.private N Attaches the private subnets.
aws_vpc_endpoint.s3 1 Gateway endpoint on the private route tables. Free, and keeps S3 (including ECR layer storage in some regions) off the NAT. Always created.
aws_security_group.vpc_endpoints 0 or 1 Allows 443 from the VPC CIDR to the interface endpoints.
aws_vpc_endpoint.interface 0 or 4 ecr.api, ecr.dkr, logs, secretsmanager, with private DNS, in every private subnet.
aws_s3_bucket.flow_logs 0 or 1 Flow log destination, named with the account id for global uniqueness. force_destroy = true.
aws_s3_bucket_public_access_block.flow_logs 0 or 1 Blocks all public access on that bucket.
aws_s3_bucket_lifecycle_configuration.flow_logs 0 or 1 Expires flow log objects after 30 days.
aws_flow_log.vpc 0 or 1 traffic_type = ALL, delivered to S3.

monitoring

Resource Count What it is for
aws_cloudwatch_log_group.api 1 /ecs/<prefix>/api
aws_cloudwatch_log_group.frontend 1 /ecs/<prefix>/frontend
aws_cloudwatch_log_group.maintenance 1 /ecs/<prefix>/maintenance
aws_cloudwatch_log_group.demo_loop 1 /ecs/<prefix>/demo-loop. Created even when the demo loop is disabled.
aws_sns_topic.alerts 1 Destination for the budget and the NAT alarms.
aws_sns_topic_subscription.alerts_email 0 or 1 Email subscription; skipped when alert_email is empty.
aws_sns_topic_policy.alerts 1 Lets budgets.amazonaws.com and cloudwatch.amazonaws.com publish to the topic.
aws_budgets_budget.daily 0 or 1 Daily cost budget with one ACTUAL notification at 100%. Skipped entirely when alert_email is empty.
aws_cloudwatch_metric_alarm.nat_bytes_out 0, 1, or N One per NAT gateway on hourly BytesOutToDestination. Skipped when nat_bytes_out_alarm_gb is 0. treat_missing_data = notBreaching.

database

Resource Count What it is for
aws_security_group.database 1 Database SG. Created with create_before_destroy.
aws_vpc_security_group_ingress_rule.db_from_ecs 1 per allowed SG Port 5432 from the ECS tasks security group.
aws_db_subnet_group.main 1 Places the instance in the private subnets.
aws_db_instance.main 1 PostgreSQL 16, gp3, encrypted, not publicly accessible, 7-day backups, final snapshot on destroy, deletion_protection = true, and a prevent_destroy lifecycle block. Performance Insights off.

secrets

Five aws_secretsmanager_secret + aws_secretsmanager_secret_version pairs, all with a 7-day recovery window.

Secret name Contents
<prefix>/db-password The raw database password
<prefix>/db-connection-string Full postgresql:// URL, injected as DATABASE_URL
<prefix>/api-internal-secret Frontend-to-API shared secret
<prefix>/kiosk-tokens Comma-separated kiosk viewer tokens
<prefix>/client-passphrase Endpoint agent passphrase

storage

Resource Count What it is for
aws_sqs_queue.ingest 1 <prefix>-osquery-ingest. 4-day retention, 5-minute visibility timeout, 20s long polling.
aws_sqs_queue.ingest_dlq 1 Dead-letter queue, 14-day retention.
aws_sqs_queue_redrive_policy.ingest 1 Moves a message to the DLQ after 3 receives.
aws_s3_bucket.main 1 <prefix>-storage, for reports, exports, and backups.
aws_s3_bucket_versioning.main 1 Versioning enabled.
aws_s3_bucket_server_side_encryption_configuration.main 1 SSE-KMS with bucket keys.
aws_s3_bucket_public_access_block.main 1 All four public-access blocks on.

auth

Resource Count What it is for
aws_cognito_user_pool.main 1 Email as username, auto-verified email, 12-character password policy requiring all four classes, optional software-token MFA, advanced security ENFORCED, email account recovery.
aws_cognito_user_pool_domain.main 1 Hosted UI at <prefix>. Globally unique across AWS.
aws_cognito_user_pool_client.web 1 Authorization-code flow with a generated secret; openid, email, profile; 1h access/id tokens, 30d refresh; prevent_user_existence_errors on.
aws_cognito_user_group.admin 1 Full administrative access.
aws_cognito_user_group.user 1 Standard read-only access.
random_password.nextauth_secret 1 64-character NextAuth session secret.
aws_secretsmanager_secret.cognito_client_id (+ version) 1 <prefix>/cognito-client-id
aws_secretsmanager_secret.cognito_client_secret (+ version) 1 <prefix>/cognito-client-secret
aws_secretsmanager_secret.nextauth_secret (+ version) 1 <prefix>/nextauth-secret

identity

Resource Count What it is for
aws_iam_role.ecs_execution 1 Assumed by ecs-tasks.amazonaws.com. Pulls images, fetches secrets, writes logs.
aws_iam_role_policy_attachment.ecs_execution_base 1 The AWS-managed AmazonECSTaskExecutionRolePolicy.
aws_iam_role_policy.ecs_execution_secrets 0 or 1 secretsmanager:GetSecretValue on exactly the eight secret ARNs the root module passes in.
aws_iam_role.ecs_task 1 The application's own role, assumed by the running container.
aws_iam_role_policy.ecs_task_logs 1 logs:CreateLogStream and logs:PutLogEvents on /ecs/<prefix>/*.

The task role has no SQS, S3, or Secrets Manager permissions. The identity module also declares an ecr_repository_arns variable that no policy uses.

containers

Registry

Resource Count What it is for
aws_ecr_repository.api 1 <project_name>-api, mutable tags, scan on push, force_delete = false.
aws_ecr_repository.frontend 1 <project_name>-frontend, same settings.
aws_ecr_lifecycle_policy.api 1 Expire beyond the last 10 images.
aws_ecr_lifecycle_policy.frontend 1 Expire beyond the last 10 images.

Note that ECR repository names use project_name only, without the environment. Two environments in one account share these repositories.

Cluster and networking

Resource Count What it is for
aws_ecs_cluster.main 1 <prefix>-cluster, Container Insights enabled.
aws_security_group.alb 1 ALB security group.
aws_vpc_security_group_ingress_rule.alb_http 1 Port 80 from 0.0.0.0/0.
aws_vpc_security_group_ingress_rule.alb_https 1 Port 443 from 0.0.0.0/0.
aws_vpc_security_group_egress_rule.alb_out 1 All outbound.
aws_security_group.ecs_tasks 1 Shared by every Fargate task in the deployment, including maintenance and demo-loop.
aws_vpc_security_group_ingress_rule.ecs_from_alb 1 TCP 0–65535 referencing the ALB security group.
aws_vpc_security_group_egress_rule.ecs_out 1 All outbound.

Load balancing

Resource Count What it is for
aws_lb.main 1 Internet-facing application load balancer in the public subnets.
aws_lb_listener.http 1 Port 80, unconditional 301 to port 443 HTTPS.
aws_lb_listener.https 1 Port 443. HTTPS with ELBSecurityPolicy-2016-08 when acm_certificate_arn is set, otherwise plain HTTP. Default action forwards to the frontend target group.
aws_lb_target_group.api 1 Port 8000, target_type = ip, health check /api/v1/health.
aws_lb_target_group.frontend 1 Port 3000, target_type = ip, health check /.
aws_lb_listener_rule.api 1 Priority 100, path pattern /api/v1/*, forwards to the API target group.

Workloads

Resource Count What it is for
aws_ecs_task_definition.api 1 Fargate, awsvpc, sized by api_cpu/api_memory, container-level curl health check with a 90s start period.
aws_ecs_task_definition.frontend 1 Fargate, awsvpc, sized by frontend_cpu/frontend_memory.
aws_ecs_service.api 1 Desired count 1, private subnets, no public IP, registered with the API target group, 50/200 deployment percentages.
aws_ecs_service.frontend 1 Same shape, frontend target group.
aws_appautoscaling_target.api 1 Min 1, max 5 tasks.
aws_appautoscaling_policy.api_cpu 1 Target-tracking on average CPU at 70%.
aws_appautoscaling_target.frontend 1 Min 1, max 5 tasks.
aws_appautoscaling_policy.frontend_cpu 1 Target-tracking on average CPU at 70%.

messaging

Resource Count What it is for
aws_apigatewayv2_api.websocket 1 WebSocket API, route selection on $request.body.action.
aws_apigatewayv2_integration.default 1 HTTP_PROXY POST to <api_url>/v1/negotiate.
aws_apigatewayv2_route.connect 1 $connect
aws_apigatewayv2_route.disconnect 1 $disconnect
aws_apigatewayv2_route.default 1 $default
aws_apigatewayv2_stage.production 1 Auto-deploying stage, throttled at 50 rps / 100 burst.

maintenance

Resource Count What it is for
aws_ecr_repository.maintenance 1 <project_name>-maintenance. Created empty — this repo does not build the image.
aws_ecr_lifecycle_policy.maintenance 1 Expire beyond the last 5 images.
aws_ecs_task_definition.maintenance 1 Fargate 256/512, EVENT_RETENTION_DAYS env, DATABASE_URL secret.
aws_scheduler_schedule.maintenance 1 cron(0 2 * * ? *) UTC, flexible time window off, runs one Fargate task in the private subnets.
aws_iam_role.scheduler 1 Assumed by scheduler.amazonaws.com.
aws_iam_role_policy.scheduler_ecs 1 ecs:RunTask on that one task definition, plus iam:PassRole on the execution and task roles.

demo-loop (only when enable_demo_loop = true)

Resource Count What it is for
aws_ecr_repository.demo_loop 1 <project_name>-demo-loop.
aws_ecr_lifecycle_policy.demo_loop 1 Expire beyond the last 5 images.
aws_ecs_task_definition.demo_loop 1 Fargate 256/512 running demo-data-generator.py --loop, with REPORTMATE_PASSPHRASE from Secrets Manager.
aws_ecs_service.demo_loop 1 Desired count 1, private subnets, 0/100 deployment percentages.

Defaults: 300 devices, batches of 15 every 120 seconds, full fleet refresh every 1800 seconds.

bootstrap (separate root module)

Resource Count What it is for
aws_s3_bucket.state 1 <project_name>-terraform-state, with prevent_destroy.
aws_s3_bucket_versioning.state 1 Versioning enabled, so a corrupted state can be rolled back.
aws_s3_bucket_server_side_encryption_configuration.state 1 SSE-KMS with bucket keys.
aws_s3_bucket_public_access_block.state 1 All four blocks on.
aws_dynamodb_table.lock 1 <project_name>-terraform-lock, pay-per-request, hash key LockID.

Not created by this module

Worth stating plainly, because the variable names suggest otherwise: there is no CloudFront distribution, no ACM certificate, no Route 53 zone or record, and no WAF. enable_custom_domain and domain_name are inert. Bring your own certificate ARN and put whatever CDN or DNS you want in front of the ALB.

Clone this wiki locally