AWS project - Module 2. Automate the build of a Static Website Hosted on AWS S3 via CodeBuild and CloudFormation
Samira Yusifova
Posted on May 18, 2023
Overview
In Module 1, 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 this module I'll show you how to automate the build and deployment via AWS CodeBuild in six simple steps:
π Step 1. Create buildspec YAML file
π Step 2. Provide CodeBuild with access to GitHub repo
π Step 3. Configure how AWS CodeBuild builds your source code
π Step 4. Create IAM role for CodeBuild project
π Step 5. Run the CloudFormation stack
π Step 6. Update frontend source code and watch how it will be built automatically
Architecture
The high-level architecture for our project is illustrated in the diagram below:
Source code
Source code for this project is available on GitHub in a public StaticWebsiteHostingToS3 repository.
π Check for frontend source code here
π Check for Module 2 CloudFormation templates here
Initial Setup
Install required tools:
π any IDE (personally I prefer Visual Studio Code) or text editor
π git
Note, you don't need to know anything about Angular or install NodeJS, NPM and Angular CLI locally. We will configure CodeBuild project with all necessary installation packages.
AWS Resources
Here is the list of AWS resources that we are going to create:
π CodeBuild project
π IAM role for CodeBuild project
π GitHub Source Credential for CodeBuild project
Step 1. Create buildspec YAML file
Buildspec is a collection of build commands and related settings, in YAML format, that CodeBuild uses to run a build. We need to create and include a buildspec YAML file as part of the source code of frontend app. In our case the file is stored in the root directory of Angular app, which is frontend/app-for-aws folder.
Shortcut: get buildspec YAML file here.
1οΈβ£ Install phase of buildspec file
Use the install phase only for installing packages in the build environment. In order to run the production build of our Angular web app, CodeBuild server needs Angular installation. Meanwhile Angular depends on NodeJS and NPM. Thus we need to specify NodeJS installation as a runtime and add commands to install Angular CLI and node modules based on dependencies specified in package.json (link):
phases:
install:
runtime-versions:
nodejs: 18
commands:
- echo Install Angular CLI and all node_modules
- cd $CODEBUILD_SRC_DIR/frontend/app-for-aws
- npm install && npm install -g @angular/cli
2οΈβ£ Build phase of buildspec file
Use the build phase for commands that CodeBuild runs during the build. In our case we need to navigate to our frontend folder and run the production build:
build:
commands:
- echo Build process started now
- cd $CODEBUILD_SRC_DIR/frontend/app-for-aws
- ng build --configuration=production
3οΈβ£ Post build phase of buildspec file
Use the post_build phase for commands that CodeBuild runs after the build. Here we can list all the files of newly created dist/app-for-aws folder for easy debugging:
post_build:
commands:
- echo Build process finished, upload artifacts to S3 bucket
- cd dist/app-for-aws
- ls -la
4οΈβ£ Artifacts of buildspec file
Artifacts represent information about where CodeBuild can find the build output and how CodeBuild prepares it for uploading to the S3 output bucket. Here we need to specify the path to dist folder so that CodeBuild won't deploy a whole frontend project to S3 bucket but files for prod build only:
artifacts:
base-directory: 'frontend/app-for-aws/dist*'
discard-paths: yes
files:
- '**/*'
discard-paths
helps to make sure to put all the files in the root folder instead of subfolder. In our case it will take all the files (including index.hmtl file) from dist/app-for-aws folder and put them in the root of S3 bucket.
Step 2. Provide CodeBuild with access to GitHub repo
1οΈβ£ In order to access source code for frontend located on your GitHub account, CodeBuild needs some access privilege. The easiest and safest way to grand CodeBuild an access to GitHub is to create a personal access token.
Navigate to your GitHub and click your profile photo, then click Settings.
In the left sidebar, scroll down and click Developer settings.
In the left sidebar, under Personal access tokens, click Tokens (classic) (since Fine-grained tokens is in beta and might not be compatible with AWS CodeBuild yet), then click Generate new token.
Select the scopes you'd like to grant this token. Note, as I've already generated a token, my screenshots shows Edit. Please ignore.
Click Generate token. Copy the new token - we will need it while running a CloudFormation stack.
2οΈβ£ In CloudFormation template we are going to create a new AWS::CodeBuild::SourceCredential resource. Here we provide information about the credentials for a GitHub.
It is recommend to use AWS Secrets Manager to store our credentials. But for the sake of simplicity (let's take baby steps) we will pass a newly generated GitHub access token as a CloudFormation parameter for now.
Parameters:
paramPersonalGitHubAccessToken:
Type: String
MinLength: 10
ConstraintDescription: Personal GitHub access token is missing
Description: Provide your personal GitHub access token for
CodeBuild to access your GitHub repo
Resources:
myCodeBuildSourceCredential:
Type: AWS::CodeBuild::SourceCredential
Properties:
AuthType: PERSONAL_ACCESS_TOKEN
ServerType: GITHUB
Token: !Ref paramPersonalGitHubAccessToken
Step 3. Configure how AWS CodeBuild builds your source code
AWS::CodeBuild::Project resource configures how AWS CodeBuild builds your source code, such as where to get the source code and which build environment to use.
First, let's give a friendly name and add description for our CodeBuild project:
Resources:
myCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: westworld-codebuild-for-website-hosting
Description: CodeBuild project for automatically build of static website hosted on s3
In Source code settings for the CodeBuild project we need to specify the GITHUB as source code's repository type, then add repo location, BuildSpec location and authorization settings for AWS CodeBuild to access the source code to be built. Remember, in Step 2 of this module we have created myCodeBuildSourceCredential
Source Credential resource - we need to use it under Auth Resource so that CodeBuild can access the specified GitHub repo.
Resources:
myCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
...
Source:
Type: GITHUB
Location: https://github.com/your-account/StaticWebsiteHostingToS3.git
GitCloneDepth: 1
BuildSpec: frontend/app-for-aws/buildspec.yml
Auth:
Resource: !Ref myCodeBuildSourceCredential
Type: OAUTH
In Triggers code settings for the CodeBuild project we want to enable AWS CodeBuild to begin automatically rebuilding the source code every time a code change is pushed to the repository. Basically we configured CodeBuild to listen to any git pushes to main branch to trigger the build.
Resources:
myCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
...
Triggers:
Webhook: true
FilterGroups:
- - Type: EVENT
Pattern: PUSH
- Type: HEAD_REF
Pattern: ^refs/heads/main # for feature branches use: ^refs/heads/feature/.*
In Environment code settings for the CodeBuild project we basically configured VM where CodeBuild is going to build the source code.
Resources:
myCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
...
Environment: # use Ubuntu standard v7
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:7.0
But how do you know which container, computer and image you need? Well, based on the code source programming language and framework you can check for Docker images here and Available runtimes here.
As we have specified NodeJS v18 in our buildspec file, we can use either Amazon Linux 2 AArch64 standard:3.0
or Ubuntu standard:7.0
image.
Based on selected image I can get Image identifier from this list.
In Artifacts code settings for the CodeBuild project we want to specify what to do with build files. In our case we want to put them to our S3 bucket that hosts the static website. Note, it's very important to disable encryption for our website files.
Resources:
myCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
...
Artifacts: # drop the build artifacts of S3 bucket that hosts static website
Type: S3
Name: '/' # store the artifact in the root of the output bucket
Location: !Sub arn:aws:s3:::westworld-codebuild-for-website-hosting
EncryptionDisabled: True #disable the encryption of artifacts in a build to see html pages
In LogsConfig code settings for the CodeBuild project we want to configure CloudWatch logs.
Resources:
myCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
...
LogsConfig:
CloudWatchLogs:
Status: ENABLED
GroupName: westworld-codebuild-for-website-hosting-CloudWatchLogs
In ServiceRole code settings for the CodeBuild project we need to create a new role with proper access - see Step 4.
Resources:
myCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
...
ServiceRole: !Ref myCodeBuildProjectRole
Step 4. Create IAM role for CodeBuild project
Final step for our CloudFormation template is to create an appropriate IAM role for CodeBuild project to get an access to S3 bucket where static website is hosted and to CloudWatch logs to stream the logs while building the project.
Resources:
myCodeBuildProjectRole:
Type: AWS::IAM::Role
Properties:
RoleName: role-for-westworld-codebuild-for-website-hosting
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Policies:
- PolicyName: policy-for-westworld-codebuild-for-website-hosting
PolicyDocument:
Version: 2012-10-17
Statement:
# statement to create/stream CloudWatch
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource:
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:westworld-codebuild-for-website-hosting-CloudWatchLogs
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:westworld-codebuild-for-website-hosting-CloudWatchLogs:*
# statement to access S3 bucket that hosts static website (CodeBuild will save Artifacts there)
- Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
Effect: Allow
Resource:
- arn:aws:s3:::your-bucket-name
- arn:aws:s3:::your-bucket-name/*
Step 5. Run the CloudFormation stack
Now we are ready to create and run CloudFormation stack based on our template for CodeBuild (see the whole template here).
Note, we have already created and run CloudFormation stack for static website and S3 - check this template for provisioning S3 bucket and step by step guide in Module 1.
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 name that you used building a stack for S3 bucket. Then run the stack. And dont forget to pass your GitHub access token as value of paramPersonalGitHubAccessToken input parameter.
Once the stack built, you should see a newly created CodeBuild project and its role under Resources tab:
You can find our newly create CodeBuild project under CodeBuild -> Build projects:
Let's navigate to our website. Remember, we have created our stack for S3 bucket - under Outputs tab you can find the link to our website (note, your link might be different from mine):
Voila! Our static website is up and running! It was built and deployed to S3 with the initial provisioning of our CodeBuild project.
Step 6. Update frontend source code and watch how it will be built automatically
Now it's time to check out build automation.
1οΈβ£ Let's make a small change (it's totally up to you) in our source code for static website.
You need to clone frontend project (unless you want to use your own project built in React, Vue or vanilla JavaScript) - check for frontend source code here.
Let's make some changes in frontend\app-for-aws\src\app\app.component.html
file. I want to change the title from
<!-- Resources -->
<h2>Good news, everyone!</h2>
to
<!-- Resources -->
<h2>Good news, everyone! CodeBuild project is up and running!</h2>
Open html file in any IDE or text editor. Make and save the changes.
Now let's push the changes to remote repo. Open your terminal and run the following commands:
# navigate to the project
cd StaticWebsiteHostingToS3
# stage the changes
git add .
# commit the changes
git commit -m"Changed the title of static website"
# push the changes to remote repo
git push
2οΈβ£ Once you pushed your changes to GitHub and navigate to our CodeBuild project on AWS Console, you should see that build was triggered automatically and its status is in progress:
Click on Build run and you will see the logs:
You can navigate to CloudWatch logs by clicking on View entire log under Build log tab:
Note, that all our logs for build were stored under newly created westworld-codebuild-for-website-hosting-CloudWatchLogs log group.
Also you can check our S3 bucket to see if the files were updated (look at Last modified date and time).
3οΈβ£ Once build status has changed to Succeeded, go ahead and refresh your website link. You should the code changes.
Note, if you don't see the changes, probably the website was cached. You can either clean the cache or open the website in incognito mode.
Cleanup
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.
Summary
Congratulations on getting this far!
In this module, we have provisioned CodeBuild project that automate the build of a static website on AWS S3. We took Infrastructure as Code approach to provisioning and managing our AWS resources by writing a template file and running stacks using AWS CloudFormation service.
Don't forget to do your happy dance!
Posted on May 18, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.