Deploying Hugo with AWS CodeBuild on Lambda Compute

Hugo Amazon Web Services AWS CodeBuild

Introduction

Hugo loves AWS CodeBuild

In this recipe, we will review how to deploy a Hugo static site using AWS CodeBuild on AWS Lambda compute. AWS Lambda compute offers optimized start-up speeds and automatic scaling for faster builds. However, there are some limitations to consider.

We will use AWS CodeBuild to build the Hugo site, deploy it to an S3 bucket, and invalidate the CloudFront cache. GitHub will be used as the source repository for the Hugo site.

Pre-requisites

Before we start, make sure you have the following:

Step 1: Create a new IAM role for CodeBuild project

We need to create a new IAM role that will be used by the CodeBuild project to sync the S3 bucket and invalidate CloudFront distribution.

Create a new policy

  1. Go to the AWS IAM console: Policies.
  2. Click on the Create policy button.
  3. Click on the JSON tab and paste the following policy document:
⚠️
Replace YOUR_BUCKET_NAME, YOUR_ACCOUNT_ID, and YOUR_DISTRIBUTION_ID with your values.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:GetBucketAcl",
                "s3:GetBucketLocation",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::YOUR_BUCKET_NAME",
                "arn:aws:s3:::YOUR_BUCKET_NAME/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateInvalidation"
            ],
            "Resource": [
                "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID" 
            ]
        }
    ]
}
  1. Click on the Next button.
  2. Fill in the policy name and description.
  3. Click on the Create policy button.

Create a new role

  1. Go back to the AWS IAM console: Roles.
  2. Click on the Create Role button.
  3. Select AWS service as the trusted entity.
  4. Choose CodeBuild as the service that will use this role.
  5. Click on the Next: Add Permissions button.
  6. Search and check the checkbox next to the policy you created previously.
  7. Click on the Next: Name, review and create button.
  8. Fill in the role name and description.
  9. Click on the Create role button.

Now you have a new IAM role that can be used by the CodeBuild project.

Step 2: Create a new CodeBuild project

Let’s create a new CodeBuild project that will build and deploy our site on every push to the GitHub repository.

Go to the AWS CodeBuild console and click on the Create build project button.

Project configuration

  1. Fill in the project name and description.
  2. Restrict number of concurrent builds this project can start to 1.

Source

  1. Select GitHub as the source provider.
  2. Click on the Connect to GitHub button and authorize AWS to access your GitHub repositories.
  3. Select your repository and branch.

Environment

  1. Select Managed Image as the environment image.
  2. Choose Lambda as the compute type.
  3. Operating system: Amazon Linux (you don’t have other options here).
  4. Runtime: Golang.
  5. Image: aws/codebuild/amazonlinux-aarch64-lambda-standard:go1.21 or most recent.
  6. Image version: Always use the latest image for this runtime version.
  7. Service role: Existing service role.
  8. Role ARN: choose the role you created in the previous step.
  9. Check the Allow AWS CodeBuild to modify this service role so it can be used with this build project checkbox.

Buildspec

Choose Insert build commands and paste the following buildspec:

ℹ️
CodeBuild Lambda environment is limited and does not allow the use of package managers like yum or rpm since they require root access. Therefore, we need to manually download and extract Hugo. For more information, refer to the Limitations of AWS Lambda compute.
⚠️
Replace YOUR_BUCKET_NAME, YOUR_REGION, and YOUR_DISTRIBUTION_ID with your values.
version: 0.2

phases:
  install:
    commands:
      # Download and extract Hugo
      - curl -Ls https://github.com/gohugoio/hugo/releases/download/v0.127.0/hugo_extended_0.127.0_linux-arm64.tar.gz -o /tmp/hugo.tar.gz
      - mkdir /tmp/hugo_0.127.0
      - tar xf /tmp/hugo.tar.gz -C /tmp/hugo_0.127.0
      - /tmp/hugo_0.127.0/hugo version
  build:
    commands:
      # Build the site using Hugo
      - /tmp/hugo_0.127.0/hugo --minify --gc
  post_build:
    commands:
      # Sync the Hugo build public folder with S3 bucket
      - aws s3 sync public/ s3://YOUR_BUCKET_NAME --region YOUR_REGION --delete
      # Invalidate CloudFront cache
      - aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths '/*'

Artifacts

Select No artifacts as the output artifact type since we are deploying the site to S3 withing the buildspec.

Logs

Optionally, you can configure CloudWatch logs for the build project. This will help you debug the build process.

Step 3: Trigger the build

Now you can trigger the build by pushing a new commit to the GitHub repository. The CodeBuild project will automatically start the build process and deploy the site to the S3 bucket. Alternatively, you can manually start the build pressing the Start build button in the CodeBuild console.

That’s it! You have successfully deployed your Hugo site using AWS CodeBuild on a Lambda environment.

CloudFormation deployment

If you prefer to deploy the CodeBuild project using CloudFormation, you can use the template below. It is parametrized with the required values.

⚠️
Consider to setup the GitHub connection manually in the AWS CodeBuild console before deploying the CloudFormation stack.
AWSTemplateFormatVersion: '2010-09-09'
Description: CodeBuild project for Hugo site deployment
Parameters:
  ProjectName:
    Type: String
    Description: CodeBuild project name
    Default: HugoSiteBuild
  SiteBucketName:
    Type: String
    Description: Site S3 bucket name
  DistributionId:
    Type: String
    Description: CloudFront distribution ID
  Region:
    Type: String
    Description: AWS region
  HugoVersion:
    Type: String
    Description: Hugo version
    Default: 0.127.0
  GitHubRepo:
    Type: String
    Description: GitHub repository URL
Resources:
  ServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CodeBuildServiceRolePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub arn:aws:logs:${Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectName}
                  - !Sub arn:aws:logs:${Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectName}:*
        - PolicyName: DeploySiteCodeBuildPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:DeleteObject
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
                  - s3:ListBucket
                Resource:
                  - !Sub arn:aws:s3:::${SiteBucketName}
                  - !Sub arn:aws:s3:::${SiteBucketName}/*
              - Effect: Allow
                Action:
                  - cloudfront:CreateInvalidation
                Resource:
                  - !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${DistributionId}
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref ProjectName
      Description: Build and deploy Hugo site
      ConcurrentBuildLimit: 1
      Environment:
        Type: ARM_LAMBDA_CONTAINER
        ComputeType: BUILD_LAMBDA_1GB
        Image: aws/codebuild/amazonlinux-aarch64-lambda-standard:go1.21
      ServiceRole: !GetAtt ServiceRole.Arn
      Source:
        Type: GITHUB
        BuildSpec: !Sub |
          version: 0.2
          phases:
            install:
              commands:
                - curl -Ls https://github.com/gohugoio/hugo/releases/download/v${HugoVersion}/hugo_extended_${HugoVersion}_linux-arm64.tar.gz -o /tmp/hugo.tar.gz
                - mkdir /tmp/hugo_${HugoVersion}
                - tar xf /tmp/hugo.tar.gz -C /tmp/hugo_${HugoVersion}
                - /tmp/hugo_${HugoVersion}/hugo version
            build:
              commands:
                - /tmp/hugo_${HugoVersion}/hugo --minify --gc
            post_build:
              commands:
                - aws s3 sync public/ s3://${SiteBucketName} --region us-east-1 --delete
                - aws cloudfront create-invalidation --distribution-id ${DistributionId} --paths '/*'
        Location: !Ref GitHubRepo
      Artifacts:
        Type: NO_ARTIFACTS
      Triggers:
        Webhook: true
        BuildType: BUILD
        FilterGroups:
          - - Type: EVENT
              Pattern: PUSH
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
          GroupName: !Sub /aws/codebuild/${ProjectName}

Conclusion

In this recipe, we learned how to deploy a GitHub sourced Hugo static site using AWS CodeBuild on a Lambda environment which is a cost-effective, easier and faster way compared to the traditional EC2 environment. We also scripted all the steps in a CloudFormation template for easy deployment and management.