Skip to content

AWS Overview

AWS is the static asset CDN for tomoda. It does not run any compute, does not hold any application database, and does not store any long-lived secrets that the platform reads at runtime (the only AWS-managed secret is the uploader's own IAM access key, used by the backend to write into S3). Everything else — GKE workloads, Postgres, Redis, Cloud Build, Artifact Registry, Secret Manager — lives in GCP.

Region

All AWS resources are in ap-northeast-1 (Tokyo), chosen for latency to the primary user base in Taiwan and East Asia. The default is set in variables.tf:

variable "aws_region" {
  default = "ap-northeast-1"
}

The one exception is the ACM certificate consumed by CloudFront, which must live in us-east-1. That is handled by a provider alias inside acm.tf — see ACM.

Resource inventory

Resource Purpose File
S3 bucket (tomoda-assets-{env}) Stores user-uploaded media and any static asset published from the backend s3.tf
CloudFront distribution Public CDN entry point for assets[-dev].tomoda.life cloudfront.tf
CloudFront Origin Access Control SigV4-signed origin requests so only CloudFront can read from S3 cloudfront.tf
ACM certificate (us-east-1) TLS cert for the asset domain, attached to the CloudFront viewer config acm.tf
IAM user tomoda-uploader-{env} Backend service identity with PutObject-only permissions on the bucket iam_uploader.tf
IAM user tomoda-eso-reader-{env} Read-only access to the uploader's secret in AWS Secrets Manager, used by External Secrets Operator iam_uploader.tf
Secrets Manager secret Holds the uploader's access key + bucket metadata, consumed by ESO iam_uploader.tf
Cloudflare DNS records (managed via the cloudflare provider) ACM validation CNAMEs + assets[-dev] CNAME pointing at CloudFront cloudflare.tf

Environments

Two environments share the same Terraform root: dev and prod. They are separated by Terraform workspace, not by a different AWS account or directory. The current workspace determines the value of var.environment, which is interpolated into every resource name (tomoda-assets-dev vs tomoda-assets-prod) and the public domain (assets-dev.tomoda.life vs assets.tomoda.life).

terraform workspace show
terraform workspace select dev   # or prod
terraform apply -var="environment=$(terraform workspace show)"

The Cloudflare API token is sourced from the cluster at apply time:

export TF_VAR_cloudflare_api_token=$(kubectl get secret external-dns-cloudflare-secret \
  -n external-dns -o jsonpath='{.data.api-token}' | base64 --decode | tr -d '\n')

State management

State lives locally on whoever runs terraform apply, under infrastructure/aws/terraform.tfstate.d/<workspace>/. There is no remote backend. This is a deliberate trade-off given the surface area — only a handful of resources, rarely changed — but it does mean:

  • Only one operator should hold the state at any given moment.
  • Losing the local state would orphan the resources; recovery would mean terraform import for each one.
  • There is no state locking. If a second apply lands while another is in flight, behaviour is undefined.

If the team ever grows or change cadence picks up, this should move to an S3 backend with DynamoDB locking.

Providers

Two providers are configured (versions.tf):

  • hashicorp/aws ~> 5.0 — default provider plus a us_east_1 alias for ACM.
  • cloudflare/cloudflare ~> 4.0 — used solely to write DNS records for ACM validation and the asset CNAME. See the Cloudflare page for the full DNS story.