Dynamically resize your images using CloudFront OriginGroup

Dynamically resize your images using CloudFront OriginGroup

Just want the code? Go here!

It’s Saturday morning and you need to take your dog (or a cat. Or a baby.) for a walk. You bring your phone which has an overcomplicated camera with you. You decided to use it.

Good boys! (not my photos, unfortunately. Got it from pexels.com)

Boom! You just became a photographer. Now, this photograph is worth sharing on your website. The problem is, the image is 6MB in size and you’re too lazy to resize it before uploading it to your website.

This blog will walk you through how to dynamically resize your image so that you don’t have to do it yourself.

Architecture Introduction

Just so we’re clear. This blog only covers the dynamic resizing image part. You have to create your website yourselves.

A diagram that explains things better than me.

From the diagram above, the only notable thing (aside from the standard CloudFront configs), is that there are 2 origins.

  1. S3 “CDN” Origin: this is where your resized files live after they were born.
  2. API Gateway Origin: this is where the resizing happens.

Note that we have 2 buckets. The first one is a “Private” bucket and the second one is a “CDN” bucket. The S3 Origin I mentioned above is using the “CDN” bucket.

The “Private” bucket is a place where you can upload your stuff. And the “CDN” bucket is a place where your users can see your stuff.

These “Private” and “CDN” buckets are like totally opinionated and optional. You can have only 1 bucket to do this.

So the flow is something like this:

  1. You upload your gorgeous picture
  2. Your picture stored in your “Private” bucket
  3. Someone wants to see your gorgeous picture
  4. CloudFront can’t find that picture in the “CDN” bucket
  5. CloudFront tries the second origin (Resizing function)
  6. Resizing function grabs that image from your “Private” bucket and resizes the image to the requested size. Then, it puts that resized image to the “CDN” bucket for subsequent access.
  7. CloudFront returns the image from your resizing function.

The code

The first step is to install serverless in an empty NPM project. We’ll orchestrate our infrastructure using that framework.

npm install -D serverless

Now create a serverless.yml file in your root project. Please refer to this file for a complete reference.

That template will produce these resources:

  1. “Private” bucket: a place where you upload your original file
  2. “CDN” bucket: a place where the resized images live for subsequent accesses
  3. Bucket Policy for “CDN” bucket: used to only allow our CloudFront distribution to access our files
  4. Origin Access Identity: an identity for our CloudFront distribution. related to #3
  5. CloudFront Distribution: our CDN
  6. API Gateway + Lambda + IAM Role: our resizing function

I’m going to talk about the CloudFront distribution (#5) and the resizing function (#6) as the other resources do not have any special stuffs related to the topic.

CloudFront Distribution

The configuration that makes this thing functional lies in the OriginGroups section. Essentially, the CDN will have a secondary origin in case the item does not exist in the primary one. In our case, our primary origin is the “CDN” bucket (S3Origin) and the secondary origin is our resizing function (APIGatewayOrigin).

Pay attention to the order of those origins. The first one will be the primary origin.

…and regarding the Quantity attribute, I don’t know why that’s required. But it’s marked as Required in the documentation.

OriginGroups:
  Items:
    - Id: DynamicImageGroup
      Members:
        Items:
          - OriginId: S3Origin
          - OriginId: APIGatewayOrigin
        Quantity: 2
      FailoverCriteria:
        StatusCodes:
          Items:
            - 403 # S3 will throw 403 when the image doesn't exist
          Quantity: 1
  Quantity: 1
Origins:
  - Id: S3Origin
    DomainName:
      Fn::GetAtt:
        - CDNBucket
        - RegionalDomainName
    S3OriginConfig:
      OriginAccessIdentity:
        Fn::Join:
          - /
          - - origin-access-identity
            - cloudfront
            - !Ref CDNOriginAccessIdentity
  - Id: APIGatewayOrigin
    DomainName:
      Fn::Join:
        [
          ".",
          [
            !Ref HttpApi,
            execute-api,
            !Ref AWS::Region,
            amazonaws.com,
          ],
        ]
    CustomOriginConfig:
      HTTPSPort: 443
      OriginProtocolPolicy: https-only

And also, use the OriginGroup’s Id in your DefaultCacheBehavior.

DefaultCacheBehavior:
  # other attributes...
  TargetOriginId: DynamicImageGroup

API Gateway + Lambda

This section of the template will produce an HTTP endpoint for your resizing function.

functions:
  cdn-image:
    runtime: nodejs12.x
    handler: src/index.handler
    memorySize: 3008
    events:
      - httpApi:
          path: /image/{width}/{image+}
          method: get
    environment:
      CDN_BUCKET_NAME: ${self:custom.userParams.CDN_BUCKET}
      PRIVATE_BUCKET_NAME: ${self:custom.userParams.PRIVATE_BUCKET}

With our HTTP configuration, you need to access the image by using an /image/{width} prefix. So, if you save your image in s3://your-private-bucket/puppy.jpg, you can access it on https://cloudfrontdomain.com/image/720/puppy.jpg

You need to do the actual resizing in the lambda function. You can do anything you like in the resizing function. Just make sure to return an image (and make sure it’s the image that the user expects).

The sample code that I made below will do these things:

  1. Get your image from the “Private” bucket
  2. Resize that image and put it to the”CDN” bucket (with sharp library)
  3. Return the image (in base64 encoded binary)

With sharp library, you need to do some sort of black magic before deploying (if you’re using Mac or Windows)

rm -rf node_modules/sharp && npm install --arch=x64 --platform=linux --target=12.13.1 sharp

This black magic is associated with a documentation if you want further readings.

Now you have to wait like 15 – 30 minutes before the stack is ready.

When you access it (e.g. https://d3h26nxsv4jv3l.cloudfront.net/image/320/two-yellow-labrador-retriever-puppies-1108099.jpg), you’ll get a resized image!

resized good boys (320px width)

Not working? I probably missed a thing or two 🙁 You can compare it to this repo. I tried to deploy several times to ensure I’m not misleading you guys.

Thanks for reading 🙂
and a shout out to Gareth Jones who makes this blog much better than it was initially.

Kendrick Kesley
kendrick.kesley@shinesolutions.com
No Comments

Leave a Reply