In this article, we are going to look at the power of infrastructure-as-code with AWS Cloudformation to deploy our website, blog or single-page application.
Our stack is production ready and contains the following characteristics
- Fast & Global: No matter the locations of our users, they will gain fast access through the Cloudfront content delivery network.
- Secure: All endpoints are protected with HTTPS to ensure the website is hosted on a secure connection.
- Automated: All changes will be 100% automated using Cloudformation. This will save us a lot of time and prevent us from needing to jump between various consoles doing manual steps.
- Reusable: Our stack is parameterised so can be easily reused for different domain names.
- Serverless: We don’t require any servers to be managed by us.
- No Idle Cost: It won’t cost us anything to run our application in an idle state. In fact it probably won’t cost anything at all unless we are going to get some serious load on our application.
Anatomy of the stack
Our stack is going to be represented in an AWS Cloudformation template and contains four key components:
- FrontendApp (
AWS::S3::Bucket
) - The bucket for storing our static web files - FrontendAppPolicy (
AWS::S3::BucketPolicy
) - The policy for allowing the files to be publicly accessible - DNSRecord (
AWS::Route53::RecordSet
) - The DNS record which will be used to access our website, blog or SPA. - CDN (
AWS::CloudFront::Distribution
) - The content delivery network distribution which is used for distributing our website, blog or SPA globally.
Prerequisites
The stacks assumes that you have:
- Route53 Hosted Zone: The hosted zone is used to manage your DNS records.
- Option 1 (New): If you don’t have a domain yet, the simplest way to get one is to purchase through the Route53 console, that way it will automatically setup the hosted zone for you.
- Option 2 (Existing): Alternatively, you can set one up for an existing domain.
- SSL Certificate: AWS certificate created via Certificate Manager. This is easy and fast to setup once you have your hosted zone.
Inputs to the automation
Our Cloudformation template will take four parameters
name
: This will be used to set your desired name for the S3 bucketdomainName
: This will be used for creating the DNS record in Route53 and configuring it in CloudfronthostedZoneId
: This is the identifier for the hosted zone, so that Cloudformation can create the DNS recordcertificate
: This is the ARN number for the AWS certificate that exists within the Certificate Manager.
Creating the infrastructure
Before we can deploy our website, blog or single-page application, we need to roll out the infrastructure to support it on Route53, Cloudfront and S3. Thanks to our automation in Cloudformation, this can be done by simply clicking on the convenient link below
Or, you can use the following to create the stack from the command line:
aws cloudformation deploy \
--template-file template.yaml --capabilities CAPABILITY_IAM \
--stack-name yourDesiredStackName --parameter-overrides \
name=yourDesiredResourceName domainName=dev.example.com \
hostedZoneId=yourHostedZoneId certificate=yourCertificate
To run the above, you need to download this file to template.yaml
Be careful of gotchas related to the name
and domainName
parameters
- S3 bucket names are global across all regions and accounts so you will need to make sure you set something unique for the
name
parameter as that is what will be used for the S3 bucket name. - The DNS record is configured to be a CNAME so the
domainName
cannot be a top-level domain. For example, www.example.com and dev.example.com will work but example.com will not. If you require a top-level domain be configured, I’d suggest to remove the resource of typeAWS::Route53::RecordSet
from the template and configure it asA
record in a separate template or manually via the console.
Deploying your website, blog or single-page application
Once our stack is created, we lastly need to deploy our static web files. Let’s say we have our files in a local folder called public
, then we would run the following to sync those to our S3 bucket. The name of our S3 bucket will match the name
parameter we used for the stack creation.
aws s3 sync public/ s3://yourBucketName
Cache invalidation
Because the content is geographically distributed and cached, changes will generally not appear immediately after they are deployed. To make the changes appear preceding a deployment we can invalidate the cache. The process of invalidation can be done for individual files or as a wildcard /*
when we want to invalidate the entire cache.
Below is a useful script that will invalidate the cache and wait for invalidation to complete. It will retrieve the distribution id from the cloudformation stack output. This will save you needing to login to the console to retrieve it.
STACK_NAME=yourStackName
DISTRIBUTION_ID=`aws cloudformation describe-stacks --stack-name $STACK_NAME | jq -r '.Stacks[0].Outputs[] | select(.OutputKey=="CDN").OutputValue'` && \
INVALIDATION_ID=`aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/*" | jq -r .Invalidation.Id` && \
aws cloudfront wait invalidation-completed --distribution-id $DISTRIBUTION_ID --id $INVALIDATION_ID
Cleaning up
Often clean up is as simple as deleting the stack via the Cloudformation console or command line. However, as we have an S3 bucket which has content in it, Cloudformation will not let us perform a deletion until the bucket is empty. We can empty the bucket and delete the stack as follows:
aws s3 rm s3://yourBucketName --recursive
aws cloudformation delete-stack --stack-name yourStackName