Vegard's blog

Improved static site deployment with Cloudfront cache invalidation

AWS Cloudfront S3 Bitbucket

I recently created this site using Eleventy as the static site generator, hosting on AWS S3 and Cloudfront as the CDN. For more information on how I set everything up see my previous blog post. I have used S3 and Cloudfront to host static content before, but I have not previously deployed changes to existing static content that I expected to be immediately available. Once I started this blog, however, I noticed that although new changes were immediately deployed to the S3 bucket it took longer for the content to be available through Cloudfront.

The reason this is happening is because Cloudfront caches the content according to a given caching policy. When I set this up I used the AWS managed caching policy, CachingOptimized, that by default uses a Time-to-live for cached content of 24 hours. The purpose of this is to serve the site faster without fetching content from S3 for every request. Although this works as intended I still want to make newly deployed blog content immediately available through Cloudfront. This can be achieved by invalidating the cache, which forces Cloudfront to fetch from S3 the next time a request is made.

Cache invalidation in deployment script

I started off by adding this to my existing blog deployment script. The first step was figuring out how to invalidate the cache using the AWS CLI. This can be done with the following command:

aws cloudfront create-invalidation --distribution-id <cloudfront-distribution-id> --paths "/*"

This invalidates the cache for all paths for the cloudfront distribution with the given ID. The Cloudfront distribution ID can be looked up on the AWS web console:

AWS Cloudfront Distribution

With this sorted out I simply added the CLI command as the last command of my deployment script.

#!/usr/bin/env bash
# Script to deploy website to S3 bucket.
# Assumes there are valid AWS credentials present in the environment
bucket_name='your-bucket-name'
cloudfront_distribution_id='your-cloudfront-distribution-id'

rm -rf _site
npm install
npx @11ty/eleventy
aws s3 sync --delete _site s3://$bucket_name/
aws cloudfront create-invalidation --distribution-id $cloudfront_distribution_id --paths "/*"

Now the Cloudfront cache is invalidated after every deploy with this script, which makes the changes immediately available like I wanted.

Bitbucket pipeline

In addition to adding the cache validation to the deployment script I wanted to add this to my Bitbucket pipeline as well. In a previous blog post I described how Open ID Connect is used to authenticate against AWS from the pipeline. For deploying the site I used a Bitbucket pipe, which extracts and uses the generated web identity token when syncing with S3.

In this case I used the CLI command for invalidating the cache and not a Bitbucket pipe. That meant that I needed to create a file that includes the web identity token that Bitbucket has generated and use an image that has the AWS CLI installed. See the Bitbucket documentation that describes this in further detail.

It is also necessary to have the following environment variables defined:

With the repository variables in place I added a second step to my Bitbucket pipeline to invalidate the cache after deploying the site to S3. The complete pipeline is shown below and I am now able to deploy the site and invalidate the cache both locally and through my Bitbucket pipeline.

pipelines:
custom:
    deploy-blog-to-s3-bucket:
    - step:
        name: Build and deploy website to S3 bucket on AWS
        image: node:18.12.1-alpine
        oidc: true
        script:
            - npm install
            - npx @11ty/eleventy
            - pipe: atlassian/aws-s3-deploy:1.1.0
            variables:
                AWS_OIDC_ROLE_ARN: $AWS_OIDC_ROLE
                S3_BUCKET: $PERSONAL_BLOG_S3_BUCKET_NAME
                LOCAL_PATH: '_site'
                WAIT: 'true'
    - step:
        name: Invalidate Cloudfront cache
        image: amazon/aws-cli
        oidc: true
        script:
            - export AWS_ROLE_ARN=$AWS_OIDC_ROLE
            - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
            - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
            - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/*"