Skip to main content

Mapping the Billing Structure and a Safe Ending: Cost Overview and Teardown Procedure

In the previous chapter, Tasuku's infrastructure grew into a structure that handles multiple environments with modules/ + envs/dev,prod. In this final chapter, we shift perspective and do two things. First, we consolidate the cost discussions scattered across each chapter into a single map. Second, we show the procedure for safely ending the learning infrastructure we've built throughout this series. Finally, we organize the topics this series placed out of scope as next steps.

12.1. Billing structure map

Ever since Chapter 1, Overview: Requirements and Architecture for the Task Management SaaS "Tasuku", this series has held to a policy of "never writing absolute prices, only showing billing dimensions and relative comparisons, and referring you to the official pricing page for details." Cost cautions for individual resources have come up in each chapter's :::warning, but we haven't yet drawn a map of the whole picture. Let's put it together here in one place.

ResourceBilling dimensionCovered in
NAT GatewayUptime + data processedVPC: CIDR Design and Cost Trade-offs for a 2-AZ, 3-Tier Network
Application Load BalancerUptime + LCU (a unit based on request volume, etc.)ALB: Layer 7 Load Balancer Basics and Your First HTTP Release
Route 53 hosted zoneFixed monthly feeRoute 53 and ACM: Publishing a Custom Domain with DNS Delegation and HTTPS
ACM certificateNone (free when integrated with ALB, etc.)Route 53 and ACM: Publishing a Custom Domain with DNS Delegation and HTTPS
ECS Fargate (web/api)vCPU/memory uptime, fluctuates with task count via Auto ScalingECS Part 1: Building a Container Runtime Foundation with Fargate / ECS Part 2: Publishing the web/api Services and Auto Scaling
RDS (Multi-AZ)Instance uptime (Multi-AZ counts as two instances) + storageRDS: Design Decisions for Managed PostgreSQL and Completing the Security Group Chain
S3 (3 buckets)Storage volume + older versions retained via versioningS3: Bucket Design and Best Practices for Managing tfstate
S3 Gateway endpointNone (free as a VPC endpoint)S3: Bucket Design and Best Practices for Managing tfstate
ECR repositoryStorage volume of stored imagesECS Part 1: Building a Container Runtime Foundation with Fargate
CloudWatch LogsLog ingestion volume + storage for the retention periodECS Part 1: Building a Container Runtime Foundation with Fargate
Secrets ManagerNumber of secrets + number of API callsRDS: Design Decisions for Managed PostgreSQL and Completing the Security Group Chain / ECS Part 2: Publishing the web/api Services and Auto Scaling

Fargate, ECR, CloudWatch Logs, and Secrets Manager were each covered mainly in terms of configuration in their individual chapters, without an explicit cost caveat. This is the first time we mention them as billing dimensions. The ACM certificate and the S3 Gateway endpoint are listed as "free" rows for contrast. Being free isn't always self-evident, so the aim is to let you check them side by side with the other paid resources.

12.2. Cost-reduction patterns

The env diff in Standardizing Deployment and Separating Environments: From Connectivity Checks to a module + envs Structure §11.5 already doubles as a cost reduction for the dev environment. RDS's multi_az = false limits billing to a single instance, and Auto Scaling's min_capacity = 1 keeps the number of continuously running tasks to a minimum. Here, we'll show a few more cost-reduction patterns you can layer on top of that.

Temporarily stopping RDS. You can stop the RDS instance during periods when it's not in use. A stop is valid for up to 7 days; after 7 days it automatically restarts.1 However, note that some charges continue even while it's stopped. Billing for provisioned storage (including Provisioned IOPS) and backup storage continues during the stop.2 The only charge that drops to zero entirely is the instance's uptime. This feature doesn't apply to Aurora MySQL, Aurora PostgreSQL, or RDS Custom.3 Since Tasuku uses ordinary PostgreSQL (not Aurora), it isn't subject to this restriction and can use this feature.

aws rds stop-db-instance --db-instance-identifier tasuku-dev-db

Reconsidering the NAT Gateway setup doesn't quite make the cut as a cost-reduction pattern. VPC: CIDR Design and Cost Trade-offs for a 2-AZ, 3-Tier Network §4.5 touched on a mode called "Regional NAT Gateway," which covers multiple Availability Zones with a single NAT Gateway. There, we noted a caveat: "whether the code ends up looking like a single gateway is a separate question from whether the actual billing ends up cheaper than provisioning two, one per AZ — we haven't confirmed the latter."4 This fact-check confirmed that the mode does in fact exist and can be used simply by specifying availability_mode = "regional",5 and that it's available in ap-northeast-1 as well.6 However, the core question Chapter 4 left open — whether it's actually cheaper than provisioning two per AZ — remains unconfirmed. That's because a Regional NAT Gateway, too, actually reserves bandwidth per Availability Zone under the hood,4 so the visible count of gateways doesn't necessarily scale proportionally with cost. We don't recommend adopting this option here either, and we're making no code changes.

The Auto Scaling minimum is already set to dev=1 in the env diff in Standardizing Deployment and Separating Environments: From Connectivity Checks to a module + envs Structure. Outside of situations that temporarily need higher performance, such as load testing, there's no reason to raise this value.

All of this is a matter of structure — "what you cut back to reduce what" — not absolute amounts. Check the actual savings against the official pricing page and your own usage.

12.3. Safe teardown

This section covers the envs/dev and envs/prod created in Standardizing Deployment and Separating Environments: From Connectivity Checks to a module + envs Structure (cleaning up the old main/ was already handled in the previous chapter, §11.8). If you naively run terraform destroy, several resources will refuse to be deleted. Let's take stock of what to check before tearing things down.

ResourceProtection settingWork required before destroy
RDS instancedeletion_protection (true in the prod environment)Change it to deletion_protection = false, re-apply, then destroy
S3 buckets (attachments, ALB logs)force_destroy not setEmpty the bucket, or temporarily set force_destroy = true and re-apply
ECR repositoryforce_delete not setDelete the images, or temporarily set force_delete = true and re-apply
Route 53 hosted zoneEverything except the default SOA/NS records must be deletedDelete the apex and api Alias records, and the ACM validation records (deleting the hosted zone itself automatically deletes the SOA/NS records) 7
ACM certificateReferenced by the listenerThe dependency on the port 443 listener is resolved automatically by Terraform's dependency graph (no extra work needed)

Destroy the tfstate bucket created by bootstrap/ last. If you delete it first, you lose the very place where the state referenced by the envs/dev and envs/prod backends is stored. This is the same chicken-and-egg relationship from S3: Bucket Design and Best Practices for Managing tfstate §5.3 — "you can't configure the backend without the bucket, and you can't put the state that creates the bucket without the backend" — showing up in reverse at destroy time. Since this bucket also doesn't have force_destroy set, just like the attachment and ALB-log buckets in the table above, you need to either empty the bucket before destroying it or temporarily set force_destroy = true and re-apply.

Let's also sort out the relationship with the moved block covered in Standardizing Deployment and Separating Environments: From Connectivity Checks to a module + envs Structure §11.6. moved was a device for reassigning an existing resource to a new address without destroying it. Destroy, by contrast, is an operation that intentionally destroys a resource. The purposes are exact opposites, but both share one thing in common: you build the Terraform operation only after accurately grasping the current state of the resources that exist now.

12.4. Looking ahead

Here are some of the items this series placed out of scope that are easy to pick up next.

In RDS: Design Decisions for Managed PostgreSQL and Completing the Security Group Chain §9.4, we noted that while Fargate's Auto Scaling in ECS Part 2: Publishing the web/api Services and Auto Scaling dynamically adjusts the task count, the cpu/memory values themselves are never revisited — and that this series likewise had no chapter that revisited the RDS instance class (db.t4g.micro). The answer to that question is: reviewing instance sizing is a concern for the operations phase. The practical order of operations is to monitor CloudWatch metrics (CPU utilization, free memory, connection count, etc.) and revisit sizing once you can see the actual load trend. There's no reason to preemptively resize at build time.

There are also other directions you could take this further:

Summary

  • We brought together the cost discussions scattered across each chapter into a single resource × billing-dimension table. Fargate, ECR, CloudWatch Logs, and Secrets Manager were mentioned as billing dimensions for the first time in this chapter
  • As cost-reduction patterns, we showed two: temporarily stopping RDS (note that storage billing continues) and the Auto Scaling minimum value. We confirmed that Regional NAT Gateway exists as an option, but the cost-reduction effect Chapter 4 left open still couldn't be resolved this time either, so we passed on adopting it
  • We organized the procedure for safely destroying envs/dev and envs/prod by resource — RDS, S3, ECR, and Route 53. The tfstate bucket gets destroyed last
  • We folded the RDS instance-sizing asymmetry left open in Chapter 9 into the operations-phase review items
  • We listed CI/CD, CloudFront, multi-region, and permissions boundary as the next steps this series placed out of scope

This series has covered the construction of everything from the VPC through RDS and the ECS services, environment separation, and finally how to safely bring it all to an end. From here, you'll dig deeper into whatever areas you need, guided by Tasuku's actual feature development and the issues that surface as you operate it.

Next steps

Footnotes

  1. Source: Stopping an Amazon RDS DB instance temporarily. States that a stop lasts up to 7 days, and it automatically restarts after 7 days.

  2. Source: same as above. States that billing for provisioned storage and backup storage continues even while stopped.

  3. Source: StopDBInstance API Reference. States that it doesn't apply to RDS Custom, Aurora MySQL, or Aurora PostgreSQL.

  4. Source: VPC: CIDR Design and Cost Trade-offs for a 2-AZ, 3-Tier Network §4.5, and Introducing Amazon VPC Regional NAT Gateway. While a single NAT Gateway automatically covers multiple Availability Zones, it also reserves bandwidth per Availability Zone under the hood, and whether the actual billing ends up cheaper than provisioning two, one per AZ, was not confirmed as of Chapter 4 and remains unresolved after this fact-check as well. 2

  5. Source: terraform MCP (hashicorp/aws provider v6.53.0, aws_nat_gateway). States that specifying availability_mode = "regional" and omitting availability_zone_address puts it into auto mode, which automatically expands AZs and EIPs upon ENI detection.

  6. Source: AWS NAT Gateway now supports regional availability (an official AWS announcement from November 2025). States that it's available in all commercial AWS Regions except the AWS GovCloud (US) Regions and the China Regions.

  7. Source: DeleteHostedZone API Reference. States that a hosted zone can't be deleted unless it contains only the default SOA and NS records, and any other records must be deleted beforehand.