Back to Resume
PrivateOctober 2023· 1 week

AWS Infrastructure Proof of Concept (Terraform)

AWS Infrastructure Proof of Concept for a Containerized Backend

RoleDevOps Engineer

A mobile application company wanted its planned AWS backend as real, reviewable infrastructure before committing to a longer build. I wrote a modular Terraform proof of concept in a single day: a VPC with public and private subnets, an ALB routing into an ECS Fargate service, and a data/messaging tier of RDS MySQL, ElastiCache Redis, and an Amazon MQ broker, each its own module wired from one root configuration. It gave the client working, inspectable code to review ahead of the fuller multi-week build their Statement of Work scoped.

The problem

The client needed a containerized backend on AWS: a load-balanced service in front of a relational database, a cache, and a message broker. Their Statement of Work described the full target environment, network isolation, autoscaling compute, monitoring, and several supporting services, but nothing existed in code yet. Going straight to a hand-built environment risks locking in decisions nobody can review. What they needed first was a proof of concept, the core network and compute path written as Terraform, so the shape of the infrastructure could be reviewed and corrected before the wider scope was built on top of it.

Constraints

  • ·One day of engineering time. Every file in the scaffold shares the same day's timestamp, a fixed, short first milestone, not the full build.
  • ·A wider Statement of Work than the milestone covered. The SOW named Route 53, S3, SES, SNS, IAM, CloudWatch monitoring, ECS autoscaling, and CI/CD testing alongside the core network and compute path.
  • ·No application yet. The container image for the ECS task did not exist, so the task definition had to be written with the image left open.
  • ·Single engineer, single AZ. Built solo, scoped to one availability zone for the proof of concept rather than a multi-AZ production layout.

Architecture

Six Terraform modules (vpc, alb, ecs, rds, elasticache, mq) composed from a single root main.tf, wired purely through module outputs, no hardcoded ids crossing module boundaries. A VPC with one public and two private subnets (single AZ) fronts an ALB that forwards into an ECS Fargate service, which shares the private tier with RDS MySQL, ElastiCache Redis, and an Amazon MQ broker.

  • module.vpc: VPC, 1 public + 2 private subnets, IGW, NAT gateway, route tables, four per-tier security groups
  • module.alb: public ALB, HTTP:80 listener, ip-type target group
  • module.ecs: Fargate cluster, task definition (4096 cpu / 8192 mb), service at desired_count 3 across both private subnets
  • module.rds: MySQL db.t2.micro instance
  • module.elasticache: single-node Redis cache.r5.large cluster with its own subnet group
  • module.mq: Amazon MQ broker + configuration (engine-type mismatch, see engineering_decisions)
  • module.s3 (excluded): a beije-static bucket, cross-client leftover, never called from root main.tf

Client traffic enters through the ALB in the public subnet and forwards into the ECS Fargate service in private subnet 1. RDS and ElastiCache sit together in private subnet 2, reachable from ECS but not from the internet. A single NAT gateway gives both private subnets outbound-only internet access.

How one request flows

  1. 01Terraform init pins the AWS provider to 5.11.0, region eu-central-1.
  2. 02module.vpc builds the network first: VPC, 1 public + 2 private subnets, IGW, NAT gateway, route tables, and four per-tier security groups, exporting every id as an output.
  3. 03module.alb consumes the ALB security group and public subnet id to create the load balancer, its HTTP:80 listener, and a target group.
  4. 04module.ecs consumes the VPC/subnet/security-group outputs plus the ALB's target group ARN, then launches a service with a fixed desired_count of 3 across both private subnets.
  5. 05module.rds and module.elasticache each take only the specific security group (and subnet, for elasticache) they need, provisioning the MySQL instance and the Redis cluster.
  6. 06module.mq takes the MQ security group and private subnet to define a broker and its configuration, though the configuration's engine type does not match the broker's, which would block a real apply.
  7. 07No apply follows. There is no state file, apply log, or CI pipeline in the folder, this is scaffold code proving the module wiring, not a deployed environment.

Engineering decisions

Six modules, wired only through outputs

What. The root configuration composes vpc, alb, ecs, rds, elasticache, and mq purely by passing each module the specific ids it needs from module.vpc's outputs, no module reaches into another's resources directly.

Why. Keeps every tier independently reviewable and swappable without touching the others' internals.

Tradeoff. Costs more upfront wiring, every dependency needs an explicit output, versus one flat file with shared locals, but a flat file does not scale past a single AZ anyway.

One private tier split by function, not by AZ

What. Two private subnets exist in a single AZ, one hosting ECS, the other hosting RDS and ElastiCache together, matching the shape sketched in the project's own working notes before any code was written.

Why. The minimum split needed to separate compute from data for a proof of concept.

Tradeoff. No failover if that AZ goes down. Reasonable for a one-day scaffold meant to prove the wiring, not a production layout.

Security groups scoped per tier, but incompletely applied

What. Four separate security groups exist for ALB, ECS task, RDS/ElastiCache, and MQ, instead of one shared group.

Why. Each tier should only accept the specific traffic it needs, not a blanket allow.

Tradeoff. The ALB group's own ingress rule ships with an empty cidr_blocks list, so despite its 'allow from anywhere' description, nothing is actually permitted in. The segmentation was designed correctly, but the rule that was supposed to open port 80 was never finished.

Amazon MQ declared, but not appliable as written

What. The broker is declared with engine_type ActiveMQ, but the configuration attached to it is written with engine_type RabbitMQ.

Why. MQ was the last of the six modules written in the day available.

Tradeoff. AWS validates that a broker's engine type matches its configuration's, so this would be rejected on a real terraform apply, not just a style issue. Named directly here rather than smoothed over.

Scope cut down to a core buildable slice

What. The client's Statement of Work asked for Route 53, S3, SES, SNS, IAM, CloudWatch, autoscaling, and CI/CD alongside the network/compute/data path. Only the network/compute/data path reached the Terraform.

Why. With one day of work, wiring the six core modules end to end took priority over the supporting services that depend on that core existing first.

Tradeoff. The scaffold proves the compute/data wiring works, but is not deployable as a complete environment against the SOW's full milestone list.

What changed along the way

  • Started from a hand-written note sketching a VPC, an ALB, and two private subnets, one for ECS and one for the data tier, before any Terraform existed. The final module layout matches it closely.
  • The Statement of Work's wider scope, Route 53, SES, SNS, IAM, CloudWatch, autoscaling, CI/CD, was never carried into code within the one day recorded, and is logged as scoped but not built rather than delivered.
  • The MQ module, written last, shipped with a broker/configuration engine mismatch that a real apply would reject.
  • The ALB security group's ingress rule was drafted with the right description but the actual cidr_blocks field was left empty.

Security and reliability

  • ·Per-tier network segmentation: four security groups scope traffic by component, though the ALB rule itself needs its cidr_blocks filled in before it does what its description says.
  • ·Private data tier: RDS and ElastiCache sit in a private subnet reachable only through the NAT gateway outbound, not exposed directly to the internet.
  • ·No live secrets in the scaffold: RDS and MQ credentials in the source were placeholder-style literal text, not production values, and were replaced with tokens during sanitization regardless.
  • ·No remote state or CI: this proof of concept has no Terraform backend, state file, or pipeline in the folder, consistent with a one-day scoping milestone, not a production environment.

Metrics

6
Terraform modules wired end to end
measured
1 day
full scaffold across all six modules, by file timestamp
measured
2
defects found and named on trace-through, not hidden
measured

What shipped

  • A modular Terraform layout (vpc/alb/ecs/rds/elasticache/mq) matching the directory structure sketched in the project's own notes, wired end-to-end from a single root main.tf.
  • VPC with 1 public and 2 private subnets, IGW/NAT/route tables, and four per-tier security groups.
  • ALB target group forwarding HTTP traffic into an ECS Fargate service spread across both private subnets.
  • RDS MySQL instance and a single-node ElastiCache Redis cluster provisioned in the private data subnet.
  • Amazon MQ broker and configuration resources drafted, though with a configuration/engine-type mismatch that was never corrected.

Lessons learned

  • ·A one-day scaffold proves the wiring, not the deployment. Naming that gap plainly, no state file, no apply log, matters more than implying a finished environment.
  • ·Security group segmentation is only as good as the specific rule inside it. Four scoped groups was the right shape, but one empty cidr_blocks field undid the ALB's own intent.
  • ·A resource's engine type has to match its attached configuration's engine type before anything else is checked. Catching that at review time is far cheaper than at apply time.
  • ·When a client's Statement of Work outscopes what a milestone can deliver, naming the gap, Route 53, SES, monitoring, autoscaling, CI/CD, protects both sides from assuming more was built than was.

Tech stack

Backend
ECS FargateApplication Load Balancer
Data & state
RDS MySQLElastiCache RedisAmazon MQ
Ops & quality
Terraform 5.11.0Amazon VPC / subnets / route tablessix-module layout wired through outputs