This series of articles provide a step-by-step guide on how to deploy a basic static website to an Amazon S3 (Simple Storage Service) bucket using AWS CloudFormation. Starting with a simple architecture, each module identifies gaps and upcoming issues, and gradually introduces additional layers of complexity to resolve those issues. The steps involved registering a custom domain, creating S3 buckets, configuring Route 53, obtaining SSL/TLS certificate, creating an Origin Access Identity (OAI), setting up CloudFront distributions, and routing DNS traffic using Route 53. By leveraging the power of CloudFormation, developers can automate the infrastructure setup and deployment process, making it easier and more efficient to launch static websites.
In this module, we are going to deploy a simple static website to S3 bucket via AWS CloudFormation in three simple steps:
👉 Step 1. Create a simple static web app via Angular
👉 Step 2. Create S3 bucket and its policy, and configure it to host a static website
👉 Step 3. Build and deploy static website manually to newly created S3 bucket (optional, you can skip it and go to Module 2 for build automation)
In next module we will automate the deployment via CodeBuild.
As I’m strongly against managing environments manually and take Infrastructure as Code for granted (if we are not on the same page, I’d suggest you to read this article first), AWS CloudFormation can be a great choice to provision and manage the complete infrastructure and AWS resources in a text file.
The high-level architecture for our project is illustrated in the diagram below:
Install required tools:
👉 any IDE (personally I prefer Visual Studio Code)
I’m going to create a simple static website using Angular. You can use any other framework/library if you want.
👉 Angular, Node.js and NPM - v16 or greater
👉 Angular CLI
# run the following command to install Angular CLI globally npm install -g @angular/cli
Here is the list of AWS resources that we are going to create:
👉 S3 bucket
👉 S3 bucket policy
Why should you host your static content on S3? Let's break it down!
☑️ Firstly, you no longer need to allocate a specific amount of storage space or plan for it, as S3 buckets automatically scale to accommodate your needs.
☑️ Moreover, since S3 operates as a serverless service, you are relieved of the burden of managing and patching servers that store your files; you simply upload and retrieve your content.
☑️ Additionally, even if your application requires a server (such as a dynamic application), it can be smaller because it doesn't need to handle requests for static content.
Let’s create a static Angular app from scratch.
1️⃣ Firstly, create a new folder and name it StaticWebsiteHostingToS3 which will store two folders:
- cloudformation folder (stores CloudFormation templates)
- frontend folder (stores static website source code)
# create a parent folder mkdir StaticWebsiteHostingToS3 cd StaticWebsiteHostingToS3 # initialize a repository git init # create a folder that stores CloudFormation templates mkdir cloudformation # create a folder that stores static website source code mkdir frontend
2️⃣ Secondly, create an initial Angular project by simply running the following commands in terminal:
# navigate to folder that should store frontend cd frontend # create a new Angular project ng new app-for-aws # check ‘y’ and ‘SCSS’ to prompt questions such as: # - Would you like to add Angular routing? (y/N) y # - Which stylesheet format would you like to use? SCSS # run the app locally cd app-for-aws ng serve
3️⃣ Navigate to http://localhost:4200/
You can customize the website as you wish. Now, let's provision some resources in AWS to host our website.
Amazon S3 is a perfect AWS service to host a static website.
In order to host our website on S3 bucket, let’s create and configure all required resources via CloudFormation.
Shortcut: get the full CloudFormation template here.
1️⃣ The following piece of code helps to create a new S3 bucket and configure it to host a static website:
Resources: myStaticWebsiteHostingBucket: Type: 'AWS::S3::Bucket' Properties: BucketName: your-bucket-name WebsiteConfiguration: IndexDocument: index.html ErrorDocument: error.html
2️⃣ Next step is to set S3 bucket permissions for website access.
When you configure a bucket as a static website, if you want your website to be public, you must disable block public access settings for the bucket and write a bucket policy that grants public read access.
That is why we are going to disable ACLs for our bucket under PublicAccessBlockConfiguration property and set the ownership of S3 bucket content to Object writer (i.e. AWS account that uploads an object owns the object and has full control over it).
OwnershipControls: Rules: - ObjectOwnership: ObjectWriter PublicAccessBlockConfiguration: BlockPublicAcls: false BlockPublicPolicy: false IgnorePublicAcls: false RestrictPublicBuckets: false
Basically we are disabling S3 Block Public Access settings to let users get a public access to the website.
One issue I faced with while configuring access settings and building the stack was the following:
Bucket cannot have public ACLs set with BlockPublicAccess enabled (Service: Amazon S3; Status Code: 400; Error Code: InvalidBucketAclWithBlockPublicAccessError)
The root of the issue was in using AccessControl property with PublicAccessBlockConfiguration property in the template. It should work with existing buckets and fail with all new buckets.
FYI, AWS has changed the default settings of ACLs of S3 bucket:
Starting in April 2023, Amazon S3 will introduce two new default bucket security settings by automatically enabling S3 Block Public Access and disabling S3 access control lists (ACLs) for all new S3 buckets. Once complete, these defaults will apply to all new buckets regardless of how they are created, including AWS CLI, APIs, SDKs, and AWS CloudFormation. These defaults have been in place for buckets created in the S3 management console since the two features became available in 2018 and 2021, respectively, and are recommended security best practices. There is no change for existing buckets. (source)
To resolve the issue, I've commented out AccessControl property:
# AccessControl: PublicRead # throws an error: Bucket cannot # have public ACLs set with BlockPublicAccess enabled
3️⃣ Next step is to create an S3 bucket policy
We need to give a public read access to S3 bucket objects. Here is the policy:
myStaticWebsiteHostingBucketPolicy: Type: 'AWS::S3::BucketPolicy' Properties: Bucket: !Ref myStaticWebsiteHostingBucket PolicyDocument: Version: 2012-10-17 Statement: - Sid: PublicReadForGetBucketObjects Effect: Allow Principal: '*' Action: 's3:GetObject' Resource: !Join - '' - - 'arn:aws:s3:::' - !Ref myStaticWebsiteHostingBucket - /*
We can turn versioning on for our S3 bucket so that in case of bugs in new version we can easily roll back the deployment to previous version. Simply add VersioningConfiguration under bucket's property:
Resources: myStaticWebsiteHostingBucket: Type: 'AWS::S3::Bucket' .... Properties: VersioningConfiguration: Status: Enabled
4️⃣ We are done with Resources, now let's output the website URL to make it easier to navigate using the link once the stack is built.
Outputs: outputWebsiteURL: Value: !GetAtt - myStaticWebsiteHostingBucket - WebsiteURL Description: Static website URL
5️⃣ We are ready to create and run CloudFormation stack based on our template.
If you need to understand how to create a stack in AWS Console, please read Hands-on AWS CloudFormation - Part 1. It All Starts Here.
I prefer to run the stack from AWS Console as it provides an end-to-end overview of a process flow. But you can always run a stack using the AWS CLI (here is how).
Upload our template file to create a stack. Change the value of paramStaticWebsiteHostingBucketName input parameter to something unique. Then run the stack.
Once the stack built, you should see a newly created bucket and it's policy under Resources tab:
Under Outputs tab you can find the link to our website.
Currently you should get 404 error as we haven't deployed static content to S3 bucket yet.
This step is optional - you can skip it and go to Module 2 for build automation.
1️⃣ First, we need to build the production version of our Angular app locally.
Open terminal for our StaticWebsiteHostingToS3 app. By default, ng build command uses the production build configuration:
cd frontend/app-for-aws ng build
A new dist folder should be autogenerated with static content.
2️⃣ In AWS Console navigate to our S3 bucket and upload all the files from dist/app-for-aws folder:
Don't forget to click on "Upload" button:
3️⃣ Now refresh the website link - our Angular app should be up and running:
You might be charged for running resources. That is why it is important to clean all provisioned resources once you are done with the stack. By deleting a stack, all its resources will be deleted as well.
Note: CloudFormation won’t delete an S3 bucket that contains objects. First make sure to empty the bucket before deleting the stack. Of course, you can automate the process by creating a Lambda function which deletes all object versions and the objects themselves. I'll try to cover that part in further article.
In this module, we have created a simple static web app and hosted it on S3 bucket. However we took baby steps to deploy our static content to S3 bucket manually. Ideally we want to use a tool that would rebuild the source code every time a code change is pushed to the repository and deploy built files to S3 bucket automatically. In Module 2 I'll show you step by step how to automate the build and deployment via AWS CodeBuild.