Skip to content
← DevOps foundations

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.

Documentation reviewed2026-10-01 · 4 min read · lab time varies
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:

hcl
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.

bash
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.

Loading saved progress…

Back up or restore this path

Progress and notes stay in this browser. A backup contains only this learning path.