DevOps Hero Part 1: Deploy a Web Application to AWS Using CloudFormation

Terris Linenbach
3 min readApr 19, 2024

The CloudFormation template below creates the infrastructure for a typical web appplication (see the diagram below) running on AWS:

  • A VPC with two public and private subnets and NAT
  • An RDS Postgres instance with RDS Proxy. It stores a random password in the Secrets Manager key /{Product}/RDS/pg/{user}
  • A public load balancer for servicing API requests at
    https://api.{DomainName}
  • An internal load balancer for service to service communication at
    https://api-internal.{DomainName}
  • An ECS Cluster with one Fargate service named API
  • A Cognito user pool with one client app for addressing the “first user” problem without resorting to seed data
  • A frontend application (React, Vue, etc.) served by AWS Amplify

Manual Steps

Setting all of this up is as automated as possible, but some manual steps are required. See the comments at the beginning of the template for more details, which are summed up as:

  1. Create a GitHub access token to enable Amplify to build the frontend application
  2. Start the CloudFormation stack creation
  3. Add a DNS NS record to the parent zone during stack creation (otherwise, it will hang for ever)
  4. Success!
  5. DesiredCount for the API service should be changed to a non-zero value when the ECR repository contains an image. We’ll address that in part 2.

Let’s Go!

To create the CloudFormation stack from the template, perform the following tasks:

  1. Install aws-cli
  2. Use aws configure sso or however you prefer to authenticate with the aws cli
  3. Save the CloudFormation template below to a file such as template.yaml
  4. Modify the parameters’ default values in the template or specify them in the following shell command
  5. Run:
aws cloudformation create-stack \
--template-body file://template.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--tags Key=AppManagerCFNStackKey,Value=YOUR_ORGANIZATION_NAME

Modularization Pros and Cons

IaC piles up quickly. Should this template be modularized into nested CloudFormation stacks like Russian dolls? There is no correct answer.

Creating a CloudFormation stack is an all-or-nothing endeavor. If creating one resource fails, all of the stack’s resources must be removed during the rollback process. Modularization doesn’t solve this problem (resource creation will fail either way) but it can result in faster try-fail-retry loops during development because rolling back can be very, very slow.

There are several arguments against modularization:

  • Someone must decide how to modularize the stack. For example: VPC, S3, Database, Load Balancers, ECS Cluster, ECS Tasks, Amplify, Cognito. This is a common stack but it’s not trivial!
  • Modularizing CloudFormation templates, unlike popular alternatives like Terraform, requires managing templates in S3 buckets. Thus, the first stack must create a bucket for the templates! All of this should be scripted, which isn’t difficult but it means more work. If you think modularization will make things easier for developers, think again.
  • The outputs of each stack (such as the VPC ARN) mist be declared explicitly. More work!
  • A single template file is convenient when enlisting ChatGPT, Q, or Copilot to make modifications to the stack
  • CloudFormation builds a DAG based on resource dependencies. When a set of dependent CloudFormation stacks are created manually, each template is serialized regardless of whether resources in different stacks are dependent upon each other. Stack creation will therefore take longer. However, the difference is probably negligible. Execution time is secondary to correctness and maintenance.

Good luck finding the optimal balance!

In the next installment of this series, we will use GitHub Actions to deploy the API service.

Diagram Generated by AWS Application Composer

--

--