AWS DevOps Hero Part 1: Deploy a Web Application to AWS Using CloudFormation
The CloudFormation template below creates the infrastructure for a typical web appplication (see the diagram below) running on AWS:
- A VPC with NAT and two public and private subnets
- 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
Let’s Go!
To create the CloudFormation stack from the template, perform the following tasks:
- Install
aws-cli
- Use
aws configure sso
or however you prefer to authenticate with the aws cli - Save the CloudFormation template below to a file such as
template.yaml
- Modify the parameters’ default values in the template or specify them in the following shell command
- Run:
aws cloudformation create-stack \
--template-body file://template.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--tags Key=AppManagerCFNStackKey,Value=YOUR_ORGANIZATION_NAME
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:
- Create a GitHub access token to enable Amplify to build the frontend application
- Start the CloudFormation stack creation
- Add a DNS NS record to the parent zone during stack creation (otherwise, it will hang for ever)
- Success!
- 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.
Next
In the next installment of this series, we will use GitHub Actions to deploy the API service.
Help From Your Friends
Read my tips on editing CloudFormation scripts with VSCode.
Appendix: 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) must be declared explicitly. More work!
- A single template file is convenient when enlisting ChatGPT, Amazon Q, or GitHub 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 balance that works for you!