AWS project - Module 2. Automate the build of a Static Website Hosted on AWS S3 via CodeBuild and CloudFormation

tiamatt

Samira Yusifova

Posted on May 18, 2023

AWS project - Module 2. Automate the build of a Static Website Hosted on AWS S3 via CodeBuild and CloudFormation

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:

Image description

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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:
    - '**/*'  


Enter fullscreen mode Exit fullscreen mode

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.

Image description

In the left sidebar, scroll down and click Developer settings.

Image description

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.

Image description

Select the scopes you'd like to grant this token. Note, as I've already generated a token, my screenshots shows Edit. Please ignore.

Image description

Image description

Image description

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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/.* 


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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.

Image description

Based on selected image I can get Image identifier from this list.

Image description

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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/*


Enter fullscreen mode Exit fullscreen mode

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.

Image description

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:

Image description

You can find our newly create CodeBuild project under CodeBuild -> Build projects:

Image description

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):

Image description

Voila! Our static website is up and running! It was built and deployed to S3 with the initial provisioning of our CodeBuild project.

Image description

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.

Image description

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>


Enter fullscreen mode Exit fullscreen mode

to



<!-- Resources -->
<h2>Good news, everyone! CodeBuild project is up and running!</h2>


Enter fullscreen mode Exit fullscreen mode

Open html file in any IDE or text editor. Make and save the changes.

Image description

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


Enter fullscreen mode Exit fullscreen mode

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:

Image description

Click on Build run and you will see the logs:

Image description

You can navigate to CloudWatch logs by clicking on View entire log under Build log tab:

Image description

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).

Image description

3️⃣ Once build status has changed to Succeeded, go ahead and refresh your website link. You should the code changes.

Image description

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!

Image description

πŸ’– πŸ’ͺ πŸ™… 🚩
tiamatt
Samira Yusifova

Posted on May 18, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related