Practical lab guide
MicroBank 3: provision local cloud dependencies
Use Terraform to own the SNS-to-SQS routes and an S3 practice bucket in LocalStack.
On this page
Give infrastructure one owner
Start the infrastructure profile from step 2. Run Terraform on the Mac, where 127.0.0.1:4566 reaches LocalStack. No real AWS account is required for this lab. The hardcoded local endpoints and dummy credentials below are intentional; do not convert this file into a real-cloud deployment by removing those settings.
Save infra/study/terraform/main.tf:
terraform {
required_version = ">= 1.12.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.66.0"
}
}
}
provider "aws" {
region = "ca-central-1"
access_key = "test"
secret_key = "test"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
s3_use_path_style = true
endpoints {
sns = "http://127.0.0.1:4566"
sqs = "http://127.0.0.1:4566"
s3 = "http://127.0.0.1:4566"
sts = "http://127.0.0.1:4566"
iam = "http://127.0.0.1:4566"
}
}
locals {
routes = {
requested = {
topic = "microbank-transactions-requested"
queue = "ledger-transactions"
}
settled = {
topic = "microbank-transactions-settled"
queue = "accounts-settlements"
}
}
}
resource "aws_sns_topic" "events" {
for_each = local.routes
name = each.value.topic
}
resource "aws_sqs_queue" "events" {
for_each = local.routes
name = each.value.queue
visibility_timeout_seconds = 30
message_retention_seconds = 86400
}
resource "aws_sqs_queue_policy" "from_topic" {
for_each = local.routes
queue_url = aws_sqs_queue.events[each.key].id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "sns.amazonaws.com" }
Action = "sqs:SendMessage"
Resource = aws_sqs_queue.events[each.key].arn
Condition = {
ArnEquals = { "aws:SourceArn" = aws_sns_topic.events[each.key].arn }
}
}]
})
}
resource "aws_sns_topic_subscription" "queue" {
for_each = local.routes
topic_arn = aws_sns_topic.events[each.key].arn
protocol = "sqs"
endpoint = aws_sqs_queue.events[each.key].arn
raw_message_delivery = false
depends_on = [aws_sqs_queue_policy.from_topic]
}
resource "aws_s3_bucket" "receipts" {
bucket = "microbank-study-receipts"
}
output "runtime" {
value = {
requested_topic_arn = aws_sns_topic.events["requested"].arn
settled_topic_arn = aws_sns_topic.events["settled"].arn
ledger_queue_url = aws_sqs_queue.events["requested"].id
receipts_bucket = aws_s3_bucket.receipts.id
}
}
There are nine resources: two topics, two queues, two queue policies, two subscriptions, and one bucket. Preserve the SNS envelope: Ledger extracts its Message property, so raw_message_delivery = true would change the consumer contract. The settlements queue deliberately has no consumer yet; observing its backlog is useful application evidence. The S3 bucket practices storage provisioning and is not claimed to receive application receipts.
Read one event route completely
Follow one key through the resource blocks. It creates a topic and queue, grants the intended topic permission to send to that queue, and subscribes the queue to the topic. References connect the generated identifiers; the explicit policy dependency handles the permission being present before subscription creation.
The two event routes account for eight resources; the independent practice bucket is the ninth. Terraform owns those API objects, while Compose owns the LocalStack process they live inside. Destroying the process first would remove the endpoint Terraform needs to perform its normal cleanup.
The runtime output is the handoff to the next lesson. terraform output -json runtime writes actual applied values to infra/study/runtime.json. Ansible reads that file and checks its expected local shape before rendering settings. Do not fill it with invented ARNs to bypass a failed apply.
Review, apply, and pass outputs forward
Use the specified provider version as the lab baseline, then commit the generated lock file. A provider schema check does not establish compatibility with every LocalStack version; record the pair you actually use.
terraform -chdir=infra/study/terraform init
terraform -chdir=infra/study/terraform fmt
terraform -chdir=infra/study/terraform validate
terraform -chdir=infra/study/terraform plan -out=local.tfplan
terraform -chdir=infra/study/terraform show local.tfplan
terraform -chdir=infra/study/terraform apply local.tfplan
terraform -chdir=infra/study/terraform output -json runtime > infra/study/runtime.json
chmod 600 infra/study/runtime.json
terraform -chdir=infra/study/terraform plan -detailed-exitcode
On a new empty emulator, review the nine planned creates. Stop and investigate unexpected existing resources or changes instead of deleting someone else's resources. After a successful apply, the last command should return 0 for no changes, 2 for a change proposal, or 1 for an error. Keep state private and retain it while the emulator resources exist.
The AWS provider authenticates with fixture credentials. Its skip flags are local-emulator settings, not cloud security practices. The queue policy records the intended topic-to-queue permission even if an emulator does not enforce every real AWS rule.
Checkpoint and sources
Explain every resource address, the dependency on the queue policy, and the runtime output contract in your notes. Step 4 consumes runtime.json without copying ARNs by hand. Sources: AWS provider custom endpoints↗, SNS subscriptions↗, SQS queue policies↗, LocalStack Terraform↗.
Your notes and evidence
Record observations, questions, or links to your work. Keep credentials out of your notes.
Back up or restore this path
Progress and notes stay in this browser. A backup contains only this learning path.